useful - Inferencia de tipo Java: la referencia es ambigua en Java 8, pero no en Java 7
type erasure java (2)
Bueno, no es como si Java7 estuviera feliz de ejecutarlo. Da un par de advertencias antes de dar el error:
jatin@jatin-~$ javac -Xlint:unchecked -source 1.7 com/company/Main.java
warning: [options] bootstrap class path not set in conjunction with -source 1.7
com/company/Main.java:19: error: no suitable method found for set(Derived,Base)
set(new Derived(), new Consumer().get());
^
method Consumer.set(Base,Derived) is not applicable
(argument mismatch; Base cannot be converted to Derived)
method Consumer.set(Derived,Collection<? extends Consumer>) is not applicable
(argument mismatch; Base cannot be converted to Collection<? extends Consumer>)
com/company/Main.java:28: warning: [unchecked] unchecked cast
return (T) new Derived();
^
required: T
found: Derived
where T is a type-variable:
T extends Base declared in method <T>get()
El problema es este:
set(new Derived(), new Consumer().get());
Cuando hacemos un
new Consumer().get()
, si miramos la firma de
get
.
Nos devuelve un Tipo
T
Sabemos que
T
alguna manera extiende
Base
.
Pero no sabemos qué es
T
específicamente.
Puede ser cualquier cosa.
Entonces, si no podemos decidir específicamente qué es
T
, entonces, ¿cómo podemos compilar?
Una forma de decirle al compilador es codificarlo y decirlo específicamente por:
set(new Derived(), new Consumer().<Derived>get());
.
La razón anterior es extremadamente extremadamente (repetida intencionalmente) peligrosa, es cuando intentas hacer esto:
class NewDerived extends Base {
public String getName(){return "name";};
}
NewDerived d = new Consumer().<NewDerived>get();
System.out.println(d.getName());
En Java7 (o cualquier versión de Java), lanzará una excepción en tiempo de ejecución:
Excepción en el hilo "main" java.lang.ClassCastException: com.company.Derived no se puede enviar a com.company.NewDerived
Debido a que
get
devuelve un objeto de tipo
Derived
pero usted ha mencionado al compilador que es de
NewDerived
.
Y no puede convertir Derived a NewDerived correctamente.
Es por eso que muestra una advertencia.
Según el error, ahora entendemos qué está mal con el
new Consumer().get()
.
Su tipo es
something that extends base
.
Haciendo
set(new Derived(), new Consumer().get());
busca un método que tome argumentos como
Derived (or any super class of it), something that extends Base
.
Ahora ambos métodos califican para el primer argumento.
Según el segundo argumento,
something that extends base
, nuevamente ambos son elegibles, ya que algo puede ser Derivado o extendido Derivado o extendido Colección.
Es por eso que Java8 arroja Error.
Según Java7, su inferencia de tipos es un poco más débil. Entonces trata de hacer algo similar,
Base base = new Consumer().get();
set(new Derived(), base);
Y nuevamente, no puede encontrar el método correcto que tome
Derived, Base
como argumentos.
Por lo tanto, arroja un error, pero por diferentes razones:
set(new Derived(), new Consumer().get()); ^ method Consumer.set(Base,Derived) is not applicable (argument mismatch; Base cannot be converted to Derived) method Consumer.set(Derived,Collection<? extends Consumer>) is not applicabl e (argument mismatch; Base cannot be converted to Collection<? extends Consu mer>)
PD: Gracias a Holger, por señalar lo incompleto de mi respuesta.
Digamos que tenemos 2 clases.
Una base vacía de clase y una subclase de esta clase
Derived
.
public class Base {}
public class Derived extends Base {}
Luego tenemos algunos métodos en otra clase:
import java.util.Collection
public class Consumer {
public void test() {
set(new Derived(), new Consumer().get());
}
public <T extends Base> T get() {
return (T) new Derived();
}
public void set(Base i, Derived b) {
System.out.println("base");
}
public void set(Derived d, Collection<? extends Consumer> o) {
System.out.println("object");
}
}
Esto se compila y se ejecuta con éxito en Java 7, pero no se compila en Java 8. El error:
Error:(8, 9) java: reference to set is ambiguous
both method set(Base,Derived) in Consumer and
method set(Derived,java.util.Collection) in Consumer match
¿Por qué funciona en Java 7, pero no en Java 8?
¿Cómo podría
<T extends Base>
igualar Colección?
El problema es que se ha mejorado la inferencia de tipos. Tienes un método como
public <T extends Base> T get() {
return (T) new Derived();
}
que básicamente dice: "la persona que llama puede decidir qué subclase de
Base
I devuelve", lo cual es una tontería obvia.
Cada compilador debería darle una advertencia no verificada sobre su tipo de conversión
(T)
aquí.
Ahora tiene una llamada al método:
set(new Derived(), new Consumer().get());
Recuerde que su método
Consumer.get()
dice "la persona que llama puede decidir lo que devuelvo".
Por lo tanto, es perfectamente correcto suponer que podría haber un tipo que extienda
Base
e implemente
Collection
al mismo tiempo.
Entonces el compilador dice "No sé si llamar a
set(Base i, Derived b)
o
set(Derived d, Collection<? extends Consumer> o)
".
Puede "arreglarlo" llamando a
set(new Derived(), new Consumer().<Derived>get());
pero para ilustrar la locura de su método, tenga en cuenta que también puede cambiarlo a
public <X extends Base&Collection<Consumer>> void test() {
set(new Derived(), new Consumer().<X>get());
}
que ahora llamará a
set(Derived d, Collection<? extends Consumer> o)
sin ninguna advertencia del compilador.
La operación insegura real ocurrió dentro del método
get
.
Entonces, la solución correcta sería eliminar el parámetro de tipo del método
get
y declarar lo que
realmente
devuelve,
Derived
.
Por cierto, lo que me irrita es su afirmación de que este código podría compilarse en Java 7. Su inferencia de tipo limitada con llamadas a métodos anidados lleva a tratar el método
get
en un contexto de invocación anidado como devolver
Base
que no se puede pasar a Un método que espera un
Derived
.
Como consecuencia, intentar compilar este código usando un compilador Java 7 compatible también fallará, pero por diferentes razones.