3

我在我的 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);
}

组件.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")
    }
}

组件用户.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在运行时调用?

4

1 回答 1

2

已编辑

我已经对这个答案进行了重大编辑,以解决早期的混淆并且更正确。

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

IComponent声明一个方法void callme(java.util.Map<String, Object> inputMap)(相当于callme(inputMap: java.util.Map[String, AnyRef]: UnitScala中),而AComponent声明void callme(java.util.Map inputMap)callme(inputMap: java.util.Map[_, _]): UnitScala中)。

也就是说,IComponent.callme接受键为 a且值为 an的Java ,同时接受键为任意类型且值为任意类型的JavaMapStringAnyRefAComponent.callme 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但是,在这种特定情况下,还有一个比仅仅省略's 类型参数要大得多的问题。

由于该方法的编译时间签名与该AComponent.callme方法的不同IComponent.callme,现在它似乎AComponent提供了两种不同的callme方法(一种采用Map<String, Object>参数,另一种采用Map参数)。然而,同时,类型擦除(在运行时删除泛型类型信息)意味着这两种方法在运行时(以及使用Java反射时)看起来也相同。因此,AComponent.callme覆盖IComponent.callme(从而履行IComponent接口的合同),同时也使任何后续调用Acomponent.callmeMap<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

但是,如果我们尝试调用callmeoriginal会发生什么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中没有办法解决这种歧义,因为这两个模棱两可的函数实际上是相同的,并且在运行时具有完全相同的签名(也使反射无用)。(如果有人知道,请随时添加评论!)

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

否则,看起来您唯一的选择是:

  • 报告原始库中的错误(AComponent.callme应该接受一个Map<String, Object>参数——用Java术语来说——不仅仅是一个Map参数),或者
  • 实现您自己的版本AComponent可以正常工作,或者
  • 使用替代库,或
  • 实现你自己的库。

更新

再多考虑一下,您也许可以通过一点技巧来实现您想要做的事情。显然,这将取决于您要做什么,以及实际IComponent和类的复杂性,但您可能会发现更改为 implement ,同时在其实现中使用实例AComponent很有用。如果不必从(in ) 派生,这应该可以工作:BComponentIComponentAComponentBComponentAComponentBComponent.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)
  }
}
于 2018-08-10T16:14:10.017 回答