"ambiguous reference to overloaded definition" 在 Scala 中使用 Java class

"ambiguous reference to overloaded definition" when using Java class in Scala

我在生成 ambiguous reference to overloaded definition 的 Scala 中使用 Java class。这是解释这个问题的代码。

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: java.util.Map[String,Object])Unit
match argument types (java.util.HashMap[String,AnyRef])
    bComponent.callme(javaMap)
                   ^
one error found

Java classes 代表一个我无法控制的库。我考虑过使用反射,但它并不能完全满足用例。 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)callme(inputMap: java.util.Map[_, _]): UnitScala 中)。

也就是说,IComponent.callme接受一个JavaMap,它的键是一个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 参数)。然而,与此同时,type erasure(在 运行 时删除通用类型信息)意味着这两种方法在 运行 时看起来也相同(以及使用 Java 反射时)。因此,AComponent.callme 覆盖了 IComponent.callme(从而履行了 IComponent 接口的契约),同时也使任何后续调用 Acomponent.callmeMap<String, Object> 实例不明确。

我们可以这样验证:注释掉BComponent中的callme定义,修改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.

提供的覆盖版本

但是,如果我们尝试在原始 aComponent 上调用 callme 会发生什么?

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: java.util.Map[_, _])Unit
 and  method callme in trait IComponent of type (x: java.util.Map[String,Object])Unit
 match argument types (java.util.HashMap[String,AnyRef])
   aComponent.callme(javaMap)
              ^

请注意,我们甚至还没有查看 BComponent

据我所知,没有办法解决 Scala 中的这种歧义,因为这两个歧义函数实际上是一样的,并且具有完全相同的功能运行 时的签名(使反射也无用)。 (如有不明之处,欢迎补充!)

因为 JavaScala 更喜欢这种通用的废话,你可能需要编写所有相关代码在 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)
  }
}