2013-01-23 6 views
6

ho qualche codice Java legacy che definisce un generico payload variabile da qualche parte al di fuori del mio controllo (cioè non posso cambiare il tipo):Mixing Scala e Java: come ottenere un parametro costruttore generico correttamente?

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

ho ricevere ad un valore payload come un parametro di metodo nel mio codice e voglio passarlo su Scala case class (da utilizzare come messaggio con un sistema di attori), ma non ottenere le definizioni corrette in modo tale da non ottenere almeno un avviso del compilatore.

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

Questo dà un avviso del compilatore "di sicurezza di tipo: contructor ... appartiene al tipo grezzo ..."

La Scala case class è definito come:

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

Come posso definire la classe del caso in modo tale che il codice venga compilato in modo pulito? (Purtroppo, cambiare il codice della classe Java Wrapper o il tipo del parametro payload non è un'opzione)

Aggiornato a chiarire l'origine del carico utile parametro

Aggiunto Per confronto, in Java posso definire un parametro proprio nello stesso modo come la variabile payload è definita:

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

e chiamarlo sulla struttura moderatamente

// Java code 
doSomethingWith(payload) 

Ma non posso istanziare per es. un oggetto Wrapper direttamente senza ricevere un avviso di tipo "raw". Qui, ho bisogno di usare un metodo static aiutante:

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

e usare questo helper statica a un'istanza di un oggetto Wrapper:

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

Soluzione

posso aggiungere un metodo di supporto simile a un oggetto compagno Scala:

// 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 
} 

e utilizzare questo da Java a un'istanza della classe ScalaMessage w/o problemi:

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

A meno che qualcuno esce con una soluzione più elegante, farò estrarre questo come una risposta ...

Grazie !

risposta

3

Credo che il problema è che in Java se si fa la seguente:

ScalaMessage msg = new ScalaMessage(payload); 

Poi si sono istanziare ScalaMessage utilizzando il suo tipo grezzo. In altre parole, si utilizza ScalaMessage come tipo non generico (quando Java ha introdotto i generici, hanno mantenuto la possibilità di trattare una classe generica come non generica, principalmente per compatibilità con le versioni precedenti).

Si dovrebbe semplicemente indicare i parametri di tipo quando si crea un'istanza ScalaMessage:

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

UPDATE: Dopo aver visto il tuo commento, in realtà ho provato in un progetto fittizio, e io in realtà un errore:

[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); 

Sembra una discrepanza tra i generici java (che possiamo emulare tramite le unità esitenziali in scala) e i generici scala. È possibile risolvere questo problema semplicemente eliminando il parametro tipo di ScalaMessage e utilizzando esistenziali, invece:

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

e poi un'istanza in Java in questo modo:

new ScalaMessage(payload) 

Questo funziona. Tuttavia, ora ScalaMessage non è più generico, il che potrebbe essere un problema se si desidera utilizzarlo con paylod più rifiniti (ad esempio uno Wrapper<? extends MyDerivedType>).

Per risolvere questo problema, facciamo ancora un altro piccolo cambiamento alla ScalaMessage:

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

E poi in Java:

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

problema risolto :)

+0

Non funzionerà perché in realtà ho un parametro metodo di tipo 'Wrapper 'in Java che viene passato nel blocco di codice di cui stiamo discutendo. Aggiornerò la domanda originale di conseguenza. –

+0

Ho fatto un aggiornamento, controllalo. –

1

Quello che stiamo vivendo è la fatto che i Java Generics sono mal implementati. Non è possibile implementare correttamente la covarianza e la contravarianza in Java e si devono utilizzare i caratteri jolly.

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

Se si fornisce un Wrapper[T], questo funzionerà correttamente e si creerà un'istanza di un ScalaMessage[T]

ciò che si vorrebbe fare è quella di essere in grado di creare un ScalaMessage[T] da un Wrapper[K] dove K<:T è sconosciuto. Tuttavia, questo è possibile solo se

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

Questa è esattamente la definizione di varianza. Poiché i generici in Java sono invarianti, l'operazione è illegale.L'unica soluzione che avete è quello di cambiare la firma del costruttore

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

Se invece il wrapper è stato implementato correttamente in Scala utilizzando il tipo di varianza

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) 
} 

Tutto verrà compilato senza problemi e con eleganza :)

+0

+1 per la spiegazione dettagliata dei sistemi di tipo Java/Scala –

Problemi correlati