【问题标题】:"ambiguous reference to overloaded definition" when using Java class in Scala在 Scala 中使用 Java 类时“对重载定义的模糊引用”
【发布时间】:2019-01-17 10:19:05
【问题描述】:

我在我的 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

【问题讨论】:

    标签: java scala reflection scala-generics


    【解决方案1】:

    已编辑

    我已经对这个答案进行了重大编辑,以解决之前的困惑并更加正确。

    我认为您正在使用的原始库已损坏,并且没有做它看起来正在做的事情。

    IComponent 声明了一个方法void callme(java.util.Map&lt;String, Object&gt; inputMap)(相当于Scala 中的callme(inputMap: java.util.Map[String, AnyRef]: Unit),而AComponent 声明了void callme(java.util.Map inputMap)Scala 中的callme(inputMap: java.util.Map[_, _]): Unit em>)。

    也就是说,IComponent.callme 接受 Java Map,其键是 String,值是 AnyRef,而 AComponent.callme 接受 Java em> 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&lt;String, Object&gt; 参数,另一种采用Map&lt;String, Object&gt;这需要一个Map 参数)。然而,同时,类型擦除(在运行时删除泛型类型信息)意味着这两个方法在运行时看起来也相同(以及使用 Java 反射时) .因此,AComponent.callme 会覆盖 IComponent.callme(从而履行IComponent 接口的合同),同时还会使用Map&lt;String, Object&gt; 实例对Acomponent.callme 进行任何后续调用不明确。

    我们可以这样验证:注释掉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$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 中的这种歧义,因为这两个模棱两可的函数实际上是相同的,并且在运行时具有完全相同的签名(使反射也没用)。 (如果有人知道,请随时添加评论!)

    由于 JavaScala 更喜欢这种泛泛的废话,因此您可能需要在 Java 中编写使用该库的所有相关代码>.

    否则,您唯一的选择似乎是:

    • 报告原始库中的错误(AComponent.callme 应该接受 Map&lt;String, Object&gt; 参数 - 在 Java 术语中 - 而不仅仅是 Map 参数),或者
    • 实现您自己的 AComponent 版本,它可以正常工作,或者
    • 使用替代库,或
    • 实现您自己的库。

    更新

    再多考虑一下,您也许可以通过一点花招来实现您想要做的事情。显然,这取决于您要做什么,以及实际 IComponentAComponent 类的复杂性,但您可能会发现更改 BComponent 以实现 IComponent 很有用,同时使用 @ 987654371@ 实例在其实现中。如果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)
      }
    }
    

    【讨论】:

    • 感谢您的详细回答和很好的解释。我正在使用 AComponent 指示的组件的旧版本,它基本上需要重写才能全面使用类型参数。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-06-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-11
    • 1970-01-01
    相关资源
    最近更新 更多