6

我有一些遗留的 Java 代码在我无法控制的某个地方定义了一个通用payload变量(即我无法更改它的类型):

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

我在我的代码中接收到这样一个payload值作为方法参数,并希望将其传递给 Scala case class (用作演员系统的消息),但没有正确定义,以至于我至少没有收到编译器警告.

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

这给出了编译器警告“类型安全:构造函数......属于原始类型......”

Scalacase class定义为:

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

如何定义案例类以使代码编译干净?(遗憾的是,更改 JavaWrapper类的代码或payload参数的类型不是一种选择)

更新以阐明有效载荷参数的来源

添加为了比较,在Java中我可以定义一个参数,就像payload定义变量一样:

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

并相应地调用它

// Java code
doSomethingWith(payload)

但是我不能在没有得到“原始类型”警告的情况下直接实例化一个 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);

ScalaMessage然后您将使用其原始类型进行实例化。或者换句话说,您将ScalaMessage其用作非泛型类型(当 Java 引入泛型时,它们保留了将泛型类视为非泛型类的能力,主要是为了向后兼容)。

您应该在实例化时简单地指定类型参数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中的exitsentials模拟)和scala泛型之间似乎不匹配。您可以通过将类型参数放入ScalaMessage并使用存在来解决此问题:

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

然后像这样在java中实例化它:

new ScalaMessage(payload)

这行得通。但是, nowScalaMessage不再是通用的,如果您想将它与更精细的有效载荷一起使用(比如 a 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

您所遇到的是 Java 泛型实现不佳的事实。您无法在 Java 中正确实现协变和逆变,您必须使用通配符。

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

如果您提供 a Wrapper[T],这将正常工作,您将创建 a 的实例ScalaMessage[T]

您想要做的是能够ScalaMessage[T]从未知的Wrapper[K]地方创建一个。K<:T然而,这只有在

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

这正是方差的定义。由于 Java 中的泛型是不变的,因此该操作是非法的。您唯一的解决方案是更改构造函数的签名

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

但是,如果 Wrapper 在 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 回答