【问题标题】:Mixing Scala and Java: How to get generically typed constructor parameter right?混合 Scala 和 Java:如何正确获取泛型类型的构造函数参数?
【发布时间】:2013-01-07 00:52:05
【问题描述】:

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

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

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

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

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

Scala case class 定义为:

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

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

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

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

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

并相应地调用它

// Java code
doSomethingWith(payload)

但我无法实例化例如直接包装对象而不会收到“原始类型”警告。在这里,我需要使用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);

除非有人提出更优雅的解决方案,否则我会提取这个作为答案...

谢谢!

【问题讨论】:

    标签: java scala generics types scala-java-interop


    【解决方案1】:

    我认为问题在于,如果你在 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 中的类型参数并改用existentials 来解决此问题:

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

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

    new ScalaMessage(payload)
    

    这行得通。但是,现在ScalaMessage 不再是通用的,如果您想将它与更精细的有效载荷一起使用(比如Wrapper&lt;? extends MyDerivedType&gt;),这可能是个问题。

    为了解决这个问题,让我们对ScalaMessage 做另一个小改动:

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

    然后在java中:

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

    问题解决了:)

    【讨论】:

    • 不起作用,因为我实际上在 Java 中有一个 Wrapper&lt;? extends SomeBaseType&gt; 类型的方法参数,它被传递到我们正在讨论的代码块中。我会相应地更新原始问题。
    【解决方案2】:

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

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

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

    您想要做的是能够从Wrapper[K] 创建一个ScalaMessage[T],其中K&lt;: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)
    }
    

    一切都会顺利而优雅地编译:)

    【讨论】:

    • +1 了解 Java/Scala 类型系统的详细解释
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-31
    • 2020-09-11
    • 1970-01-01
    • 2018-04-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多