Estoy usando una clase Java en mi Scala que genera ambiguous reference to overloaded definition
. Aquí está el código para explicar este problema.
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)
}
Cuando intento compilar BComponent.scala
y ComponentUser.scala
la compilación falla con el mensaje abajo.
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
Las clases Java representan una biblioteca que no tengo ningún control sobre. He considerado el uso de la reflexión, pero no acaba de servir al caso de uso. super[AComponent].callme
También, no resuelve el problema. ¿Cómo puede la situación se resuelva de manera que las compilaciones de código y AComponent.callme
se invoca en tiempo de ejecución?
EDITADO
He editado esta respuesta significativamente a resolver la confusión anterior y para ser más correcto.
Creo que la biblioteca original que está trabajando con está roto, y no hacer lo que parece estar haciendo.
IComponent
declara un método void callme(java.util.Map<String, Object> inputMap)
(que es equivalente a callme(inputMap: java.util.Map[String, AnyRef]: Unit
en Scala ), mientras que AComponent
declara void callme(java.util.Map inputMap)
( callme(inputMap: java.util.Map[_, _]): Unit
en Scala ).
Es decir, IComponent.callme
acepta un Java Map
cuya clave es una String
y cuyo valor es una AnyRef
, mientras que AComponent.callme
acepta un Java Map
cuya clave es cualquier tipo y cuyo valor es cualquier tipo .
Por defecto, el de Java compilador acepta esto sin queja. Sin embargo, si se compila con la -Xlint:all
opción, la de Java compilador emitirá la advertencia:
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) {
Sin embargo, hay un problema mucho mayor en el caso concreto que simplemente omitiendo Map
's argumentos de tipo.
Debido a que la signatura de tiempo de compilación de los AComponent.callme
difiere del método de la del IComponent.callme
método, ahora parece que AComponent
proporciona dos diferentes callme
métodos (uno que toma un Map<String, Object>
argumento, y uno que toma un Map
argumento). Sin embargo, al mismo tiempo, el tipo de borrado (eliminación de la información de tipo genérico en tiempo de ejecución) significa que los dos métodos también son idénticas en tiempo de ejecución (y cuando se utiliza Java reflexión). Por lo tanto, AComponent.callme
las anulaciones IComponent.callme
(cumpliendo así con el IComponent
contrato de interfaz), mientras que también hace las llamadas subsiguientes a Acomponent.callme
una Map<String, Object>
instancia ambigua.
Podemos comprobar esto de la siguiente manera: comentario a cabo la callme
definición de BComponent
y cambiar el contenido del ComponentUser.scala
archivo de la siguiente manera:
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)
}
Cuando se ejecuta, el programa ahora da salida:
Called AComponent.callme with AComponent
¡Excelente! Hemos creado una AComponent
instancia, lo convertimos en una IComponent
referencia, y cuando llamamos callme
, fue inequívoca (un IComponent
sólo tiene un método llamado callme
) y ejecuta la versión sustituida proporcionada por AComponent
.
Sin embargo, ¿qué ocurre si tratamos de llamar callme
en el 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! Esta vez, nos sale un error de la Scala compilador, que es un poco más rigurosa respecto a los parámetros de tipo que 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)
^
Tenga en cuenta que ni siquiera hemos mirado BComponent
todavía.
Por lo que puedo decir, no hay manera de solucionar este ambigüedad en Scala , ya que las dos funciones ambiguas en realidad son una y la misma, y tienen la misma firma exacta en tiempo de ejecución (haciendo inútil la reflexión también). (Si alguien sabe de otro modo, no dude en enviar un comentario!)
Dado que Java es más feliz con este disparate genérico que Scala , puede que tenga que escribir todo el código relevante que utiliza esta biblioteca de Java .
De lo contrario, parece que sus únicas opciones son:
- Informar de un error en la biblioteca original (
AComponent.callme
debe aceptar unMap<String, Object>
argumento en Java términos, no sólo unMap
argumento), o - Poner en práctica su propia versión de
AComponent
que funciona correctamente, o - Utilizar una biblioteca alternativa, o
- Poner en práctica su propia biblioteca.
ACTUALIZAR
Haber pensado en ello un poco más, usted podría ser capaz de lograr lo que busca hacer con un poco de juego de manos. Está claro que dependerá de lo que estamos tratando de hacer, y la complejidad de lo real IComponent
y de AComponent
las clases, pero es posible que le resulte útil para cambiar BComponent
a aplicar IComponent
, durante el uso de un AComponent
ejemplo en su aplicación. Esto debería funcionar siempre y cuando BComponent
no tenga que ser derivado de AComponent
(en 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)
}
}