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.
IComponentdeclares a methodvoid callme(java.util.Map<String, Object> inputMap)(which is equivalent tocallme(inputMap: java.util.Map[String, AnyRef]: Unitin Scala), whileAComponentdeclaresvoid callme(java.util.Map inputMap)(callme(inputMap: java.util.Map[_, _]): Unitin Scala).That is,
IComponent.callmeaccepts a JavaMapwhose key is aStringand whose value is anAnyRef, whileAComponent.callmeaccepts a JavaMapwhose 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:alloption, 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.callmemethod differs from that of theIComponent.callmemethod, it now appears thatAComponentprovides two differentcallmemethods (one that takes aMap<String, Object>argument, and one that takes aMapargument). 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.callmeoverridesIComponent.callme(thereby fulfilling theIComponentinterface's contract), while also making any subsequent calls toAcomponent.callmewith aMap<String, Object>instance ambiguous.We can verify this as follows: comment out the
callmedefinition inBComponentand change the contents of theComponentUser.scalafile as follows:When run, the program now outputs:
Great! We created an
AComponentinstance, converted it to anIComponentreference, and when we calledcallme, it was unambiguous (anIComponentonly has a single method namedcallme) and executed the overridden version provided byAComponent.However, what happens if we try to call
callmeon 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
BComponentyet.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.callmeshould accept aMap<String, Object>argument—in Java terms—not just aMapargument), orAComponentthat 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
IComponentandAComponentclasses, but you might find it useful to changeBComponentto implementIComponent, while using anAComponentinstance in its implementation. This should work provided thatBComponentdoesn't have to be derived fromAComponent(inBComponent.scala):