我在Scala中使用Java类,该类生成ambiguous reference to overloaded definition
。这是解释此问题的代码。
IComponent.java
package javascalainterop;
import java.util.Map;
public interface IComponent {
public void callme(Map<String, Object> inputMap);
}
AComponent.java
package javascalainterop;
import java.util.Map;
public class AComponent implements IComponent {
String message;
public AComponent(String message) {
this.message = message;
}
@Override
public void callme(Map inputMap) {
System.out.println("Called AComponent.callme with " + message);
}
}
BComponent.scala
package javascalainterop
import java.util.{Map => JMap}
class BComponent(inputMessage: String) extends AComponent(inputMessage) {
override def callme(inputMap: JMap[_, _]) {
println(s"Called BComponent.callme with $inputMessage")
}
}
ComponentUser.scala
package javascalainterop
import java.util.{HashMap => JHashMap}
object ComponentUser extends App {
val bComponent = new BComponent("testmessage")
val javaMap = new JHashMap[String, AnyRef]
bComponent.callme(javaMap)
}
当我尝试编译
BComponent.scala
和ComponentUser.scala
时,编译失败并显示以下消息。javascalainterop/ComponentUser.scala:8: error: ambiguous reference to overloaded definition,
both method callme in class BComponent of type (inputMap: java.util.Map[_, _])Unit
and method callme in trait IComponent of type (x$1: java.util.Map[String,Object])Unit
match argument types (java.util.HashMap[String,AnyRef])
bComponent.callme(javaMap)
^
one error found
Java类代表一个我无法控制的库。我已经考虑过使用反射,但是它并不能完全满足用例的要求。
super[AComponent].callme
也不能解决问题。如何解决这种情况,以便编译代码并在运行时调用AComponent.callme
? 最佳答案
编辑过
我已对该答案进行了重大修改,以解决早期的困惑并变得更加正确。
我认为您正在使用的原始库已损坏,并且未按照其正在执行的操作进行操作。IComponent
声明方法void callme(java.util.Map<String, Object> inputMap)
(与Scala中的callme(inputMap: java.util.Map[String, AnyRef]: Unit
等效),而AComponent
声明void callme(java.util.Map inputMap)
(Scala中的callme(inputMap: java.util.Map[_, _]): Unit
)。
也就是说,IComponent.callme
接受Java Map
,其键是String
,其值是AnyRef
,而AComponent.callme
接受Java Map
,其键是任何类型,且值是任何类型。
默认情况下,Java编译器会毫无保留地接受它。但是,如果使用-Xlint:all
选项进行编译,则Java编译器将发出警告:
javascalainterop/AComponent.java:12:1: found raw type: java.util.Map
missing type arguments for generic class java.util.Map<K,V>
public void callme(Map inputMap) {
但是,在这种特定情况下,存在一个更大的问题,不仅仅是忽略
Map
的类型参数。因为
AComponent.callme
方法的编译时签名与IComponent.callme
方法的不同,所以现在看来AComponent
提供了两种不同的callme
方法(一种采用Map<String, Object>
参数,一种采用Map
参数)。但是,同时,类型擦除(在运行时删除通用类型信息)意味着这两种方法在运行时(以及使用Java反射时)看起来也相同。因此,AComponent.callme
会覆盖IComponent.callme
(从而履行IComponent
接口的合同),同时还会使带有Acomponent.callme
实例的对Map<String, Object>
的任何后续调用都不明确。我们可以如下验证:在
callme
中注释掉BComponent
定义,并如下更改ComponentUser.scala
文件的内容:package javascalainterop
import java.util.{HashMap => JHashMap}
object ComponentUser extends App {
//val bComponent = new BComponent("testmessage")
val javaMap = new JHashMap[String, AnyRef]
//bComponent.callme(javaMap)
// Test what happens when calling callme through IComponent reference.
val aComponent = new AComponent("AComponent")
val iComponent: IComponent = aComponent
iComponent.callme(javaMap)
}
运行时,程序现在输出:
Called AComponent.callme with AComponent
大!我们创建了一个
AComponent
实例,将其转换为IComponent
引用,当我们调用callme
时,它是明确的(一个IComponent
仅具有一个名为callme
的方法),并执行了AComponent
提供的覆盖版本。但是,如果我们尝试在原始
callme
上调用aComponent
会发生什么?package javascalainterop
import java.util.{HashMap => JHashMap}
object ComponentUser extends App {
//val bComponent = new BComponent("testmessage")
val javaMap = new JHashMap[String, AnyRef]
//bComponent.callme(javaMap)
// Test what happens when calling callme through each reference.
val aComponent = new AComponent("AComponent")
val iComponent: IComponent = aComponent
iComponent.callme(javaMap)
aComponent.callme(javaMap)
}
哦!这次,我们从Scala编译器中得到了一个错误,该错误在类型参数方面比Java更为严格:
javascalainterop/ComponentUser.scala:14:14: ambiguous reference to overloaded definition,
both method callme in class AComponent of type (x$1: java.util.Map[_, _])Unit
and method callme in trait IComponent of type (x$1: java.util.Map[String,Object])Unit
match argument types (java.util.HashMap[String,AnyRef])
aComponent.callme(javaMap)
^
请注意,我们甚至还没有查看
BComponent
。据我所知,在Scala中没有办法解决这种歧义,因为这两个模棱两可的函数实际上是一个相同的,并且在运行时具有完全相同的签名(也使反射无用)。 (如果有人知道,请随时添加评论!)
由于Java比Scala更适合使用这种普通的废话,因此您可能需要用Java编写使用该库的所有相关代码。
否则,您似乎唯一的选择是:
AComponent.callme
应该接受Map<String, Object>
参数(以Java术语表示,而不仅仅是Map
参数)或AComponent
或更新
再多考虑一下,您就可以轻而易举地实现您想要的目标。显然,这将取决于您要执行的操作以及实际
IComponent
和AComponent
类的复杂性,但是您可能会发现更改BComponent
来实现IComponent
很有用,同时在实现中使用AComponent
实例。只要没有必要从BComponent
派生AComponent
(在BComponent.scala
中),这应该可以工作:package javascalainterop
import java.util.{Map => JMap}
class BComponent(inputMessage: String) extends IComponent {
// Create an AComponent instance and access it as an IComponent.
private final val aComponent: IComponent = new AComponent(inputMessage)
// Implement overridden callme in terms of AComponent instance.
override def callme(inputMap: JMap[String, AnyRef]): Unit = {
println(s"Called BComponent.callme with $inputMessage")
aComponent.callme(inputMap)
}
}