2013-01-23 36 views
6

我有某處定義了一個通用​​可變我的控制以外一些遺留Java代碼(即我不能改變其類型):混合Scala和Java:如何獲得一般類型的構造函數參數?

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

接收這種​​值作爲方法參數在我的代碼中,並希望將它傳遞給Scala case class(用作具有actor系統的消息),但是不要正確定義這些定義,以至於我至少得不到編譯器警告。

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

這給出了一個編譯器警告 「類型安全:構造器...屬於原始類型......」

斯卡拉case class被定義爲:

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

我如何定義代碼編寫乾淨的代碼類? (不幸的是,改變了Java Wrapper類的代碼或​​參數的類型不是一個選項)

更新澄清有效載荷參數

的原點加爲了比較,在Java中我可以以同樣的方式定義參數作爲​​變量定義:

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

,並調用它艾科rdingly

// Java code 
doSomethingWith(payload) 

但我不能實例化例如一個Wrapper對象直接沒有得到「原始類型」警告。在這裏,我需要使用static helper方法:

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

,並使用該靜態輔助實例化一個Wrapper對象:

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

解決方案

我可以添加一個類似的helper方法到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 
} 

,並使用這個從Java實例化ScalaMessage類W/O問題:

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

除非有人想出了一個更好的解決方案,我將提取這是一個答案......

謝謝!

回答

3

我認爲問題是,在Java中,如果你做到以下幾點:

ScalaMessage msg = new ScalaMessage(payload); 

然後你使用它的原始類型實例ScalaMessage。換句話說,您使用ScalaMessage作爲非泛型類型(當Java引入泛型時,它們保留將泛型類視爲非泛型類的能力,主要是爲了向後兼容)。

實例ScalaMessage時,您應該簡單地指定類型參數:

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

UPDATE:看到您的評論後,我居然試圖在一個虛擬的項目,其實我得到一個錯誤:

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

這似乎是java泛型(我們可以通過scala中的exitsentials模擬)和scala泛型之間的不匹配。你可以只是刪除在ScalaMessage類型參數,並使用existentials而不是解決這個問題:

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

然後實例它在Java中是這樣的:

new ScalaMessage(payload) 

這工作。但是,現在ScalaMessage不再是通用的,如果您想要將它與更精細的Paylod一起使用(例如Wrapper<? extends MyDerivedType>),則可能會出現問題。

要解決這個問題,讓我們做另一個小的變化,以ScalaMessage

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

然後在Java中:

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

問題解決了:)

+0

不會工作,因爲我實際上有一個類型爲'Wrapper <?的方法參數?擴展了Java中的SomeBaseType>,它傳入我們正在討論的代碼塊中。我會相應更新原始問題。 –

+0

我做了一個更新,檢查它。 –

1

你所遇到的是Java泛型實現不好的事實。你不能在Java中正確實現協變和逆變,你必須使用通配符。

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

如果提供Wrapper[T],這將正常工作,你會創造你想要做什麼的ScalaMessage[T]

一個實例是能夠從Wrapper[K]其中K<:T創建ScalaMessage[T]是未知的。不過,這隻有在

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

這正是方差的定義。由於Java中的泛型不變,操作是非法的。你有唯一的解決辦法是改變構造方法的簽名

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

然而,如果包裝是在正確使用Scala的類型差異

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

一切都會順利,典雅編譯:)

實施
+0

+1以瞭解Java/Scala類型系統的詳細說明 –

相關問題