我在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.scalaComponentUser.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
  • 使用备用库或
  • 实现您自己的库。

  • 更新

    再多考虑一下,您就可以轻而易举地实现您想要的目标。显然,这将取决于您要执行的操作以及实际IComponentAComponent类的复杂性,但是您可能会发现更改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)
      }
    }
    

    10-08 17:58