2013-01-23 2 views
6

Ich habe einige ältere Java-Code, der eine generische payload Variable irgendwo außerhalb meiner Kontrolle definiert (dh ich nicht seine Art ändern kann):Mischen von Scala und Java: Wie generisch typisierte Konstruktor Parameter richtig erhalten?

// Java code 
Wrapper<? extends SomeBaseType> payload = ... 

I einen solchen payload Wert als Methodenparameter erhalten in meinem Code und möchte es an eine Scala weiterleiten case class (als Nachricht mit einem Actor-System zu verwenden), aber nicht die Definitionen richtig, so dass ich nicht mindestens eine Compiler-Warnung erhalten.

// still Java code 
ScalaMessage msg = new ScalaMessage(payload); 

Dies ergibt eine Compiler-Warnung "Typ Sicherheit: contructor ... gehört zur rohen Art ..."

Die Scala case class wie folgt definiert ist:

// Scala code 
case class ScalaMessage[T <: SomeBaseType](payload: Wrapper[T]) 

Wie kann ich definieren die Fall-Klasse, so dass der Code sauber kompiliert? (Leider, Ändern Sie den Code der Java Wrapper Klasse oder den Typ des payload Parameter ist keine Option)

Aktualisiert den Ursprung der Nutzlast Parameter

Added Zum Vergleich zu klären, in Java kann ich einen Parameter nur in der gleichen Art und Weise definieren, wie die payload Variable definiert ist:

// Java code 
void doSomethingWith(Wrapper<? extends SomeBaseType> payload) {} 

und nennen es acco rdingly

// Java code 
doSomethingWith(payload) 

Aber ich kann z. ein Wrapper-Objekt direkt ohne eine Warnung "Rohtyp" zu erhalten. Hier, ich brauche eine static Hilfsmethode zu verwenden:

static <T> Wrapper<T> of(T value) { 
    return new Wrapper<T>(value); 
} 

und verwende diese statischen Helfer ein Wrapper Objekt zu instanziiert:

// Java code 
MyDerivedType value = ... // constructed elsewhere, actual type is not known! 
Wrapper<? extends SomeBaseType> payload = Wrapper.of(value); 

Lösung

ich eine ähnliche Hilfsmethode hinzufügen zu einem Scala-Begleitobjekt:

// Scala code 
object ScalaMessageHelper { 
    def apply[T <: SomeBaseType](payload: Wrapper[T]) = 
     new ScalaMessage(payload) 
} 
object ScalaMessageHelper2 { 
    def apply[T <: SomeBaseType](payload: Wrapper[T]) = 
     ScalaMessage(payload) // uses implicit apply() method of case class 
} 

und nutzen diese von Java die ScalaMessage Klasse w/o Probleme zu instanziiert:

// Java code 
ScalaMessage msg = ScalaMessageHelper.apply(payload); 

Es sei denn, jemand mit einer eleganteren Lösung kommt, ich dies als eine Antwort extrahieren ...

Danke !

Antwort

3

ich glaube, das Problem ist, dass in Java, wenn Sie wie folgt vorgehen:

ScalaMessage msg = new ScalaMessage(payload); 

Dann Sie ScalaMessage mit seiner rohen Art sind Instanziierung. Oder mit anderen Worten, Sie verwenden ScalaMessage als nicht generischen Typ (wenn Java Generika eingeführt hat, behielten sie die Fähigkeit, eine generische Klasse als eine nicht-generische Klasse zu behandeln, hauptsächlich aus Gründen der Abwärtskompatibilität).

Sie sollten einfach die Typparameter angeben, wenn ScalaMessage Instanziierung:

// (here T = MyDerivedType, where MyDerivedType must extend SomeBaseType 
ScalaMessage<MyDerivedType> msg = new ScalaMessage<>(payload); 

UPDATE: Nachdem Sie Ihren Kommentar zu sehen, versuchte ich es tatsächlich in einem Dummy-Projekt, und ich tatsächlich einen Fehler:

[error] C:\Code\sandbox\src\main\java\bla\Test.java:8: cannot find symbol 
[error] symbol : constructor ScalaMessage(bla.Wrapper<capture#64 of ? extends bla.SomeBaseType>) 
[error] location: class test.ScalaMessage<bla.SomeBaseType> 
[error]  ScalaMessage<SomeBaseType> msg = new ScalaMessage<SomeBaseType>(payload); 

Es scheint eine Diskrepanz zwischen Java-Generika (die wir durch Exitsentials in Scala emulieren können) und Scala-Generics. Sie können dieses Problem beheben, indem Sie einfach den Typ-Parameter in ScalaMessage fallen und mit Existenziale statt:

case class ScalaMessage(payload: Wrapper[_ <: SomeBaseType]) 

und es dann so in Java instanziiert:

new ScalaMessage(payload) 

Dies funktioniert. Aber jetzt ist ScalaMessage nicht mehr generisch, was ein Problem sein könnte, wenn Sie es mit raffinierteren Paylods (sagen wir Wrapper<? extends MyDerivedType>) verwenden möchten.

Um dies zu beheben, lassen Sie uns noch zu ScalaMessage eine weitere kleine Änderung tun:

case class ScalaMessage[T<:SomeBaseType](payload: Wrapper[_ <: T]) 

Und dann in Java:

ScalaMessage<SomeBaseType> msg = new ScalaMessage<SomeBaseType>(payload); 

Problem gelöst :)

+0

Funktioniert nicht, da ich tatsächlich einen Methodenparameter vom Typ 'Wrapper 'in Java, das in den Block des Codes übergeben wird, den wir diskutieren. Ich werde die ursprüngliche Frage entsprechend aktualisieren. –

+0

Ich habe ein Update gemacht, überprüfen Sie es. –

1

Was Sie erleben, ist die Tatsache, dass Java Generics schlecht implementiert sind. Sie können Kovarianz und Kontravarianz in Java nicht korrekt implementieren und Sie müssen Platzhalter verwenden.

case class ScalaMessage[T <: SomeBaseType](payload: Wrapper[T]) 

Wenn Sie eine Wrapper[T] bieten, wird diese korrekt funktionieren, und Sie werden eine Instanz eines ScalaMessage[T]

erstellen Was Sie tun möchten, ist in der Lage sein, eine ScalaMessage[T] von einem Wrapper[K] zu schaffen, wo K<:T ist unbekannt. Dies ist jedoch nur möglich, wenn

Wrapper[K]<:Wrapper[T] for K<:T 

Dies ist genau die Definition der Varianz. Da Generika in Java invariant sind, ist die Operation illegal.Die einzige Lösung, die Sie haben, ist die Unterschrift des Konstrukteurs zu ändern

class ScalaMessage[T](wrapper:Wrapper[_<:T]) 

Wenn jedoch der Wrapper wurde mit Typ Varianz

class Wrapper[+T] 
class ScalaMessage[+T](wrapper:Wrapper[T]) 

object ScalaMessage { 
    class A 
    class B extends A 

    val myVal:Wrapper[_<:A] = new Wrapper[B]() 

    val message:ScalaMessage[A] = new ScalaMessage[A](myVal) 
} 

Alles wird zusammenstellen glatt und elegant richtig in Scala umgesetzt :)

+0

+1 für die detaillierte Erklärung der Java/Scala-Typ-Systeme –

Verwandte Themen