2013-01-23 35 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类型系统的详细说明 –

相关问题