6

私のコントロールの外のどこかにpayloadジェネリック変数を定義するレガシーJavaコードがあります(つまり、その型を変更することはできません):

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

コードでメソッドパラメーターなどの値を受け取りpayload、それをScalaに渡したいcase class (アクターシステムでメッセージとして使用するため)が、少なくともコンパイラー警告が表示されないように定義を正しく取得しない。

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

これにより、コンパイラに「型安全性:コンストラクタ...はraw型に属します...」という警告が表示されます。

Scalacase classは次のように定義されます。

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

コードがきれいにコンパイルされるようにケースクラスを定義するにはどうすればよいですか?(残念ながら、JavaWrapperクラスのコードやpayloadパラメーターのタイプを変更することはできません)

ペイロードパラメータの起源を明確にするために更新されました

追加payload比較のために、Javaでは、変数が定義されているのとまったく同じ方法でパラメーターを定義できます。

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

それに応じてそれを呼び出す

// Java code
doSomethingWith(payload)

ただし、「raw型」の警告が表示されない限り、たとえばWrapperオブジェクトを直接インスタンス化することはできません。ここでは、staticヘルパーメソッドを使用する必要があります。

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

解決

同様のヘルパーメソッドを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問題のないクラスをインスタンス化します。

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

誰かがよりエレガントな解決策を思い付かない限り、私はこれを答えとして抽出します...

ありがとうございました!

4

2 に答える 2

3

問題は、Javaで次のことを行う場合です。

ScalaMessage msg = new ScalaMessage(payload);

次に、 rawタイプScalaMessageを使用してインスタンス化します。つまり、非ジェネリック型として使用します(Javaがジェネリックを導入したとき、主に下位互換性のために、ジェネリッククラスを非ジェネリッククラスとして扱う機能を維持していました)。ScalaMessage

インスタンス化するときは、タイプパラメータを指定するだけですScalaMessage

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

更新:あなたのコメントを見た後、私は実際にダミープロジェクトでそれを試しました、そして私は実際にエラーを受け取ります:

[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のexistentialsを介してエミュレートできる)とscalaジェネリックの間の不一致のようです。これを修正するには、typeパラメーターをドロップし、ScalaMessage代わりにexistentialsを使用します。

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

次に、次のようにJavaでインスタンス化します。

new ScalaMessage(payload)

これは機能します。ただし、現在ScalaMessageはジェネリックではなくなっています。これは、より洗練されたペイロッド(たとえばWrapper<? extends MyDerivedType>)で使用する場合に問題になる可能性があります。

これを修正するために、さらに別の小さな変更を加えましょうScalaMessage

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

そしてJavaで:

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

問題が解決しました :)

于 2013-01-23T14:21:47.567 に答える
2

あなたが経験しているのは、JavaGenericsが不十分に実装されているという事実です。Javaで共変性と反変性を正しく実装することはできず、ワイルドカードを使用する必要があります。

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

を指定するとWrapper[T]、これは正しく機能し、のインスタンスを作成しますScalaMessage[T]

あなたがしたいのは、未知の場所ScalaMessage[T]からを作成できるようにすることです。ただし、これは次の場合にのみ可能ですWrapper[K]K<: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)
}

すべてがスムーズかつエレガントにコンパイルされます:)

于 2013-01-23T16:05:01.413 に答える