2012-03-03 20 views
9

w czystym środowisku Scala, mogę wykonać następujące czynności gdybym chciał „dodać” do metody fabryki do istniejącego obiektu:Jak dodać metody fabryki do istniejącej klasy Java w Scala

object Test 

object Extensions { 

    object RichTest { 
     def someFactory = new Test() 
    } 
    implicit def fromTest(t: Test.type) = RichTest 

} 

... 

import Extensions._ 
val t = Test.someFactory 

I potrzebowałby takiej funkcjonalności w połączeniu z istniejącą klasą Java. W moim konkretnym przykładzie, chciałbym dodać metodę fabryczną fromLocation do klasy com.google.android.maps.GeoPoint (i myślę, że każdy deweloper Android będzie wiedział dlaczego miałoby to być przydatne ;-)).

Jednak, gdy próbuję coś zrobić jak

implicit def fromGeoPoint(t: GeoPoint.type) = RichTest 

otrzymuję komunikat o błędzie informujący

niezgodność typów; znaleziono: com.google.android.maps.GeoPoint.type (typu bazowego z obiektu com.google.android.maps.GeoPoint) wymagane: AnyRef

Więc zastanawiam się, czy istnieje jakikolwiek sposób, jak powyżej podejście może realizowane - a to zapewnia niejawnego konwersji z Location do GeoPoint być korzystny sposób Scala więc Location można stosować, gdy wymagany jest GeoPoint?


Zgodnie z wnioskiem w komentarzach, scenariusz Zastosowanie:

// Callback you get from the GPS 
override def onLocationChanged(l: Location) { 
    // You want to put a marker on a map, hence a GeoPoint is required 
    val gp: GeoPoint = GeoPoint.fromLocation(location) 
    val item = new OverlayItem(gp, ...) 
    .... 
} 

Jednakże, należy pamiętać, że jest to tylko jeden konkretny przykład dla ogólnego problemu ;-)

+0

Czy możesz podać przykład kodu, który użyłby tej metody fabrycznej? Na przykład z GeoPoints i Lokalizacjami. Naprawdę nie rozumiem tego pytania. – Fixpoint

+0

@Fixpoint done ;-) – Leo

Odpowiedz

2

Począwszy od wersji 2.9.1 Scala to nie jest możliwe przedłużyć klasę Java za pomocą metody fabrycznej.

Jednak istnieją trzy alternatywne rozwiązania:

  1. Zapis Scala compiler plugin aby pomieścić wymagane zmiany, a następnie rozszerzenie klasy Java.
  2. Utwórz samodzielną metodę fabryczną.
  3. Należy unikać stosowania metody fabrycznej i dodawać niejawną konwersję, aby obiekt lokalizacji mógł być zawsze używany zamiast GeoPoint.

Osobiście poszedłbym do trzeciej opcji, jeśli użyłem Location ->GeoPoint lub jakąkolwiek inną przemianę dużo czasu, zwłaszcza w sytuacji gdy konwersja zawsze można zrobić bez wyjątków i interfejsy klasy nie zakładka.

implicit def geoPointFromLocation(l: Location):GeoPoint = new GeoPoint... 

override def onLocationChanged(l: Location) {   
    val gp: GeoPoint = l // let Scala compiler do the conversion for you 
    val item = new OverlayItem(gp, ...) 
    .... 
    // or just pass the location to OverlayItem constructor directly 
    val item2 = new OverlayItem(l, ...) 
} 
0

co chcesz robić to jest niemożliwe. Ponieważ klasa Java nie jest obiektem Scala, nie ma możliwości jej powiększenia metodami.

Jedynym punktem widzę, aby prace jak najłatwiejsze jest stworzenie obiektu w Scala i dokonać wykwalifikowany importu dla klasy Java:

import test.{ Test => JTest } 
object Test { 
    def anyMethodHere ... 
} 
+0

... który absolutnie NIE jest tym, czego chcę. Metoda fabularna w Javie to metoda statyczna; odpowiednik Scali to metody obiektowe, prawda? Mogę nazywać statyczne metody Java w Scali, więc musi istnieć jakaś konwersja statyczna do obiektu. Na koniec chcę dodać statyczną metodę do klasy Java poprzez Scalę. Co oznacza, że ​​nowe słowo kluczowe jest tym, czego NIE chcę. – Leo

+0

@Mef: Przepraszam, że przegapiłem ten punkt. Zaktualizowałem moją odpowiedź, chociaż nie mogę ci naprawdę pomóc. – sschaef

2

Dobre pytanie! Niestety, nie sądzę, że jest to możliwe. Ponieważ w Javie nie ma możliwości użycia statycznych części klasy jako wartości, nie ma powodu, aby typ statycznych elementów klasy Java rozszerzał AnyRef. I niestety, aby ten obiekt Java rozszerzyć AnyRef byłoby użyć niejawna konwersja, które wymagają obiekt Java rozszerzyć AnyRef ...

Chciałbym być udowodnione źle chociaż!

Aktualizacja: nie można tego zrobić w Javie, i nie myślę, że najlepszym rozwiązaniem jest stworzenie własnego statycznego klasy, w której, aby dodać metod fabrycznych. Na przykład rozważ List w Guava.

Aktualizacja: tutaj jest pełna przykładów różnic między Java i Scala (co Dario opisywał).

# vim Test.java 
class Test { 
    public static final int foo = 1; 
    public final int bar = 2; 
} 

# javac Test.java 
# ls 

Test.class Test.java 

# javap Test 

Compiled from "Test.java" 
class Test { 
    public static final int foo; 
    public final int bar; 
    Test(); 
} 

porównaniu z Scala:

# vim Test.scala 

object Test { 
    val foo = 1 
} 

class Test { 
    val bar = 2 
} 

# javac Test.scala 
# ls 

Test$.class Test.class Test.scala 

# javap Test 

public class Test implements scala.ScalaObject { 
    public static final int foo(); 
    public int bar(); 
    public Test(); 
} 

# javap Test$ 

Compiled from "Test.scala" 
public final class Test$ implements scala.ScalaObject { 
    public static final Test$ MODULE$; 
    public static {}; 
    public int foo(); 
} 
2

To nie jest możliwe, ponieważ w Scala na object ... będzie pojedyncza instancja udostępniane za pomocą metod statycznych w klasie java. Na przykład.

object Foo { 
    def bar = 2 
} 

stanie się coś takiego:

public final class Foo { 
    public static int bar() { 
    return Foo..MODULE$.bar(); 
    } 
} 

public final class Foo$ 
    implements ScalaObject 
{ 
    public static final MODULE$; 

    static 
    { 
    new(); 
    } 

    public int bar() 
    { 
    return 2; 
    } 

    private Foo$() 
    { 
    MODULE$ = this; 
    } 
} 

Co zostaje przekazana do metody niejawnej konwersji jest w tym przypadku Foo..MODULE $, który jest instancją typu Foo $. Statyka Java nie ma podstawowej instancji singleton i dlatego nie może zostać przekazana do funkcji do przekonwertowania na inny typ.

Mam nadzieję, że to pomaga zrozumieć, dlaczego nie jest to możliwe ;-).

1

Mimo że nie można dodać metody poprzez niejawną konwersję do tego rodzaju obiektów singleton, można użyć niejawnego parametru do rzeczywistego tworzenia. Na przykład:

trait Factory1[-A,+B] { 
    def buildFrom(a: A): B 
} 

trait Factory2[-A1,-A2,+B] { 
    def buildFrom(a1: A1, a2: A2): B 
} 

object Factories { 
    implicit object GeoPointFromLocation extends Factory1[Location,GeoPoint] { 
    def buildFrom(location: Location): GeoPoint = //actual code 
    } 
    implicit object GeoPointFromXY extends Factory2[Double,Double,GeoPoint] { 
    def buildFrom(x: Double, y: Double): GeoPoint = //actual code 
    } 
} 

object GeoPoint { 
    def from[A](a: A)(implicit fac: Factory1[A,GeoPoint]) = fac.buildFrom(a) 
    def from[A,B](a: A, b: B)(implicit fac: Factory2[A,B,GeoPoint]) = fac.buildFrom(a,b) 
} 

import Factories._ 
val loc = new Location(...) 
val gp1 = GeoPoint.from(loc) 
val gp2 = GeoPoint.from(101.0, -12.6) 

Więc fabryki są nadal „statyczne”, jak wydaje się ich spodziewać, ale można wzbogacić lub zmienić zachowanie, bez konieczności zmiany GeoPoint obiekt. Możesz na przykład importować specjalne fabryki podczas testowania.

To podejście jest używane w bibliotece Scala, na przykład do budowania właściwego typu kolekcji przy stosowaniu ogólnych metod Traversable, takich jak map.

+0

... co prowadzi do "importowanego GeoPointa jest trwale ukryte z definicji obiektu GeoPoint w pakiecie ...", więc myślę, że tak naprawdę nie działa w praktyce :-( – Leo

0

Nie jest to możliwe, ale można mieć obiekt towarzysz samej nazwie klasy w obiekcie pakietu i używać go jak chcesz,

import com.google.android.maps.GeoPoint 

package object com.xyz.mappy.maps { 
    object GeoPoint {   
    def from(....): GeoPoint = new GeoPoint(...) 
    } 
} 


................................. 

package com.xyz.mappy.maps 

import com.google.android.maps.GeoPoint 

class MapRenderer { 

    val geopoint = GeoPoint.from(...) 

}  
Powiązane problemy