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 methodvoid callme(java.util.Map<String, Object> inputMap)
(which is equivalent tocallme(inputMap: java.util.Map[String, AnyRef]: Unit
in Scala), whileAComponent
declaresvoid callme(java.util.Map inputMap)
(callme(inputMap: java.util.Map[_, _]): Unit
in Scala).That is,
IComponent.callme
accepts a JavaMap
whose key is aString
and whose value is anAnyRef
, whileAComponent.callme
accepts a JavaMap
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: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 theIComponent.callme
method, it now appears thatAComponent
provides two differentcallme
methods (one that takes aMap<String, Object>
argument, and one that takes aMap
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
overridesIComponent.callme
(thereby fulfilling theIComponent
interface's contract), while also making any subsequent calls toAcomponent.callme
with aMap<String, Object>
instance ambiguous.We can verify this as follows: comment out the
callme
definition inBComponent
and change the contents of theComponentUser.scala
file as follows:When run, the program now outputs:
Great! We created an
AComponent
instance, converted it to anIComponent
reference, and when we calledcallme
, it was unambiguous (anIComponent
only has a single method namedcallme
) and executed the overridden version provided byAComponent
.However, what happens if we try to call
callme
on the originalaComponent
?Uh oh! This time, we get an error from the Scala compiler, which is a little more rigorous regarding type parameters than Java:
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 aMap<String, Object>
argument—in Java terms—not just aMap
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
andAComponent
classes, but you might find it useful to changeBComponent
to implementIComponent
, while using anAComponent
instance in its implementation. This should work provided thatBComponent
doesn't have to be derived fromAComponent
(inBComponent.scala
):