Inferencia de tipo Java: la referencia es ambigua en Java 8, pero no en Java 7

Digamos que tenemos 2 clases. Una Base clase vacía, y una subclase de esta clase Derived .

 public class Base {} public class Derived extends Base {} 

Entonces 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 get() { return (T) new Derived(); } public void set(Base i, Derived b) { System.out.println("base"); } public void set(Derived d, Collection o) { System.out.println("object"); } } 

Esto se comstack y se ejecuta satisfactoriamente en Java 7, pero no se comstack 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 siempre coincidir con la Colección?

El problema es que la inferencia de tipo se ha mejorado . Tienes un método como

 public  T get() { return (T) new Derived(); } 

que básicamente dice, “el que llama puede decidir qué subclase de Base volveré”, lo cual es una tontería obvia. Cada comstackdor debería darle una advertencia sin marcar sobre su tipo de yeso (T) aquí.

Ahora tiene una llamada a método:

 set(new Derived(), new Consumer().get()); 

Recuerde que su método Consumer.get() dice “la persona que llama puede decidir qué devuelvo”. Por lo tanto, es perfectamente correcto suponer que podría haber un tipo que extienda la Base e implemente la Collection al mismo tiempo. Entonces el comstackdor dice “No sé si llamar al set(Base i, Derived b) o al set(Derived d, Collection o) “.

Puede “arreglarlo” llamando a set(new Derived(), new Consumer().get()); pero para ilustrar la locura de su método, tenga en cuenta que también puede cambiarlo a

 public > void test() { set(new Derived(), new Consumer().get()); } 

que ahora llamará a set(Derived d, Collection o) sin ninguna advertencia del comstackdor. 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 comstackrse en Java 7. Su inferencia de tipo limitada con llamadas a métodos nesteds conduce a tratar el método get en un contexto de invocación nested como devolver Base que no se puede pasar a un método que espera un Derived . Como consecuencia, tratar de comstackr este código utilizando un comstackdor conforme a Java 7 también fallará, pero por diferentes razones.

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) is not applicable (argument mismatch; Base cannot be converted to Collection) 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 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 puede el comstackdor?

Una forma de decirle al comstackdor, es mediante la encoding física y diciéndole específicamente por: set(new Derived(), new Consumer().get()); .

El motivo anterior es extremadamente extremadamente (repetido intencionalmente) peligroso, es cuando intentas hacer esto:

 class NewDerived extends Base { public String getName(){return "name";}; } NewDerived d = new Consumer().get(); System.out.println(d.getName()); 

En Java7 (o cualquier versión de Java), lanzará una excepción en el tiempo de ejecución:

Excepción en el hilo “principal” java.lang.ClassCastException: com.company.Derived no se puede convertir en com.company.NewDerived

Porque get devuelve un objeto de tipo Derived pero ha mencionado al comstackdor que es de NewDerived . Y no puede convertir derivada a NewDerived correctamente. Es por eso que muestra una advertencia.


Según el error, ahora entendemos lo que está mal con el new Consumer().get() . Su tipo es something that extends base . Hacer 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 son aptos para el primer argumento. Según el segundo argumento, something that extends base , de nuevo ambos son elegibles ya que algo puede ser Derivado o extender Derivado o extender Colección. Esta es la razón por la cual Java8 arroja un error.

Según Java7, su tipo de inferencia es un poco más débil. Entonces intenta 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) is not applicabl e (argument mismatch; Base cannot be converted to Collection) 

PD: Gracias a Holger, por señalar lo incompleto de mi respuesta.