I am using a Java class in my Scala which generates ambiguous reference to overloaded definition
. Here is the code to explain this problem.
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)
}
When I try to compile BComponent.scala
and ComponentUser.scala
the compilation fails with message below.
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
The Java classes represent a library which I have no control over. I have considered using reflection but it doesn't quite serve the use case. super[AComponent].callme
also doesn't resolve the issue. How can the situation be resolved so that the code compiles and AComponent.callme
is invoked at runtime?
EDITED
I've edited this answer significantly to resolve earlier confusion and to be more correct.
I think the original library that you're working with is broken, and not doing what it appears to be doing.
IComponent
declares a method void callme(java.util.Map<String, Object> inputMap)
(which is equivalent to callme(inputMap: java.util.Map[String, AnyRef]: Unit
in Scala), while AComponent
declares void callme(java.util.Map inputMap)
(callme(inputMap: java.util.Map[_, _]): Unit
in Scala).
That is, IComponent.callme
accepts a Java Map
whose key is a String
and whose value is an AnyRef
, while AComponent.callme
accepts a Java Map
whose key is any type and whose value is any type.
By default, the Java compiler accepts this without complaint. However, if compiled with the -Xlint:all
option, the Java compiler will issue the warning:
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) {
However, there's a far bigger problem in this specific case than merely omitting Map
's type arguments.
Because the compile time signature of the AComponent.callme
method differs from that of the IComponent.callme
method, it now appears that AComponent
provides two different callme
methods (one that takes a Map<String, Object>
argument, and one that takes a Map
argument). Yet, at the same time, type erasure (the removal of generic type information at runtime) means that the two methods also look identical at runtime (and when using Java reflection). So, AComponent.callme
overrides IComponent.callme
(thereby fulfilling the IComponent
interface's contract), while also making any subsequent calls to Acomponent.callme
with a Map<String, Object>
instance ambiguous.
We can verify this as follows: comment out the callme
definition in BComponent
and change the contents of the ComponentUser.scala
file as follows:
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)
}
When run, the program now outputs:
Called AComponent.callme with AComponent
Great! We created an AComponent
instance, converted it to an IComponent
reference, and when we called callme
, it was unambiguous (an IComponent
only has a single method named callme
) and executed the overridden version provided by AComponent
.
However, what happens if we try to call callme
on the original 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)
}
Uh oh! This time, we get an error from the Scala compiler, which is a little more rigorous regarding type parameters than 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)
^
Note that we haven't even looked at BComponent
yet.
So far as I can tell , there is no way to workaround this ambiguity in Scala, since the two ambiguous functions are actually one and the same, and have the exact same signature at runtime (making reflection useless too). (If anyone knows otherwise, please feel free to add a comment!)
Since Java is happier with this generic nonsense than Scala, you might need to write all of the relevant code that uses this library in Java.
Otherwise, it looks like your only options are to:
AComponent.callme
should accept a Map<String, Object>
argument—in Java terms—not just a Map
argument), orAComponent
that works correctly, orUPDATE
Having thought about it a little more, you might be able to achieve what you're looking to do with a little sleight of hand. Clearly, it will depend upon what you're trying to do, and the complexity of the actual IComponent
and AComponent
classes, but you might find it useful to change BComponent
to implement IComponent
, while using an AComponent
instance in its implementation. This should work provided that BComponent
doesn't have to be derived from AComponent
(in 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)
}
}