¿Por qué la inferencia de tipo genérico Java 8 elige esta sobrecarga?

Considere el siguiente progtwig:

public class GenericTypeInference { public static void main(String[] args) { print(new SillyGenericWrapper().get()); } private static void print(Object object) { System.out.println("Object"); } private static void print(String string) { System.out.println("String"); } public static class SillyGenericWrapper { public  T get() { return null; } } } 

Imprime “Cadena” en Java 8 y “Objeto” en Java 7.

Hubiera esperado que esto fuera una ambigüedad en Java 8, porque ambos métodos sobrecargados coinciden. ¿Por qué el comstackdor elige print(String) después de JEP 101 ?

Justificado o no, esto rompe la compatibilidad con versiones anteriores y el cambio no se puede detectar en el momento de la comstackción. El código simplemente se comporta de manera disimulada de manera diferente después de actualizar a Java 8.

NOTA: El SillyGenericWrapper se llama “tonto” por una razón. Estoy tratando de entender por qué el comstackdor se comporta de la manera que lo hace, no me digas que el envoltorio tonto es un mal diseño en primer lugar.

ACTUALIZACIÓN: también he intentado comstackr y ejecutar el ejemplo bajo Java 8 pero usando un nivel de lenguaje Java 7. El comportamiento fue consistente con Java 7. Eso se esperaba, pero aún sentía la necesidad de verificarlo.

Las reglas de inferencia de tipo han recibido una revisión importante en Java 8; más notablemente, la inferencia del tipo de objective ha sido mucho mejor. Entonces, mientras que antes de Java 8, el sitio del argumento del método no recibía ninguna inferencia, por defecto a Object, en Java 8 se deduce el tipo aplicable más específico, en este caso String. El JLS para Java 8 introdujo un nuevo capítulo Capítulo 18. Tipo de inferencia que falta en JLS para Java 7.

Las versiones anteriores de JDK 1.8 (hasta 1.8.0_25) tenían un error relacionado con la resolución de métodos sobrecargados cuando el comstackdor compiló código que, según JLS, debería haber producido un error de ambigüedad. ¿Por qué este método se sobrecarga de forma ambigua? Como señala Marco13 en los comentarios

Esta parte del JLS es probablemente la más complicada

lo cual explica los errores en versiones anteriores de JDK 1.8 y también el problema de compatibilidad que ves.


Como se muestra en el ejemplo de Java Tutoral ( Type Inference )

Considera el siguiente método:

 void processStringList(List stringList) { // process stringList } 

Supongamos que desea invocar el método processStringList con una lista vacía. En Java SE 7, la siguiente statement no se comstack:

 processStringList(Collections.emptyList()); 

El comstackdor Java SE 7 genera un mensaje de error similar al siguiente:

 List cannot be converted to List 

El comstackdor requiere un valor para el argumento tipo T, por lo que comienza con el valor Object. En consecuencia, la invocación de Collections.emptyList devuelve un valor de tipo List, que no es compatible con el método processStringList. Por lo tanto, en Java SE 7, debe especificar el valor del valor del argumento de tipo de la siguiente manera:

 processStringList(Collections.emptyList()); 

Esto ya no es necesario en Java SE 8. La noción de qué es un tipo de destino se ha ampliado para incluir argumentos de método, como el argumento del método processStringList. En este caso, processStringList requiere un argumento de tipo List

Collections.emptyList() es un método genérico similar al método get() de la pregunta. En Java 7, el método print(String string) ni siquiera es aplicable a la invocación del método, por lo que no participa en el proceso de resolución de sobrecarga . Mientras que en Java 8 ambos métodos son aplicables.

Esta incompatibilidad vale la pena mencionar en la Guía de compatibilidad para JDK 8 .


Puede consultar mi respuesta para una pregunta similar relacionada con la resolución de métodos sobrecargados Ambigüedad de sobrecarga de métodos con las primitivas terciarias condicionales y no compartidas de Java 8

De acuerdo con JLS 15.12.2.5 Elegir el método más específico :

Si más de un método de miembro es accesible y aplicable a una invocación de método, es necesario elegir uno para proporcionar el descriptor para el envío del método en tiempo de ejecución. El lenguaje de progtwigción Java usa la regla de que se elige el método más específico.

Entonces:

Un método aplicable m1 es más específico que otro método aplicable m2, para una invocación con expresiones de argumento e1, …, ek, si cualquiera de los siguientes es verdadero:

  1. m2 es genérico, y se deduce que m1 es más específico que m2 para las expresiones de argumento e1, …, ek según §18.5.4.

  2. m2 no es genérico, y m1 y m2 son aplicables por invocación estricta o suelta, y donde m1 tiene tipos de parámetros formales S1, …, Sn y m2 tiene tipos de parámetros formales T1, …, Tn, el tipo Si es más específico que Ti para el argumento ei para todo i (1 ≤ i ≤ n, n = k).

  3. m2 no es genérico, y m1 y m2 son aplicables por invocación de aria variable, y donde los primeros k tipos de parámetros de aridad variable de m1 son S1, …, Sk y los primeros k tipos de parámetros de aridad variables de m2 son T1, .. ., Tk, el tipo Si es más específico que Ti para el argumento ei para todo i (1 ≤ i ≤ k). Además, si m2 tiene parámetros k + 1, entonces el tipo de parámetro de aridad variable k + 1’th de m1 es un subtipo del tipo de parámetro de aridad variable k + 1’th de m2.

Las condiciones anteriores son las únicas circunstancias bajo las cuales un método puede ser más específico que otro.

Un tipo S es más específico que un tipo T para cualquier expresión si S <: T (§4.10).

La segunda de tres opciones coincide con nuestro caso. Dado que String es un subtipo de Object ( String <: Object ), es más específico. Por lo tanto, el método en sí es más específico . Siguiendo el JLS, este método también es estrictamente más específico y más específico, y lo elige el comstackdor.

En java7, las expresiones se interpretan de abajo hacia arriba (con muy pocas excepciones); el significado de una sub-expresión es una especie de “contexto libre”. Para una invocación a un método, los tipos de argumentos se resuelven primero; el comstackdor luego usa esa información para resolver el significado de la invocación, por ejemplo, para elegir un ganador entre los métodos sobrecargados aplicables.

En java8, esa filosofía ya no funciona, porque esperamos usar lambda implícito (como x->foo(x) ) en todas partes; los tipos de parámetros lambda no están especificados y deben inferirse del contexto. Eso significa que, para las invocaciones de métodos, a veces los tipos de parámetros del método deciden los tipos de argumentos.

Obviamente hay un dilema si el método está sobrecargado. Por lo tanto, en algunos casos, es necesario resolver la sobrecarga del método primero para elegir un ganador, antes de comstackr los argumentos.

Ese es un cambio importante; y algún código antiguo como el suyo será víctima de incompatibilidad.

Una solución es proporcionar una “tipificación de destino” al argumento con “contexto de conversión”

  print( (Object)new SillyGenericWrapper().get() ); 

o al igual que la sugerencia de @ Holger, proporcione el parámetro de tipo get() para evitar la inferencia todos juntos.


La sobrecarga de métodos Java es extremadamente complicada; el beneficio de la complejidad es dudoso. Recuerde, la sobrecarga nunca es una necesidad; si son métodos diferentes, puede darles diferentes nombres.

En primer lugar, no tiene nada que ver con la anulación, pero tiene que lidiar con la sobrecarga.

Jls ,. La Sección 15 proporciona mucha información sobre cómo exactamente el comstackdor selecciona el método sobrecargado

El método más específico se elige en tiempo de comstackción; su descriptor determina qué método se ejecuta realmente en tiempo de ejecución.

Entonces al invocar

 print(new SillyGenericWrapper().get()); 

El comstackdor elige String version over Object porque el método de print que utiliza String es más específico que el que toma Object . Si hubiera Integer lugar de String , se seleccionará.

Además, si desea invocar el método que toma Object como parámetro, puede asignar el valor de retorno al parámetro de tipo object ejemplo,

 public class GenericTypeInference { public static void main(String[] args) { final SillyGenericWrapper sillyGenericWrapper = new SillyGenericWrapper(); final Object o = sillyGenericWrapper.get(); print(o); print(sillyGenericWrapper.get()); } private static void print(Object object) { System.out.println("Object"); } private static void print(Integer integer) { System.out.println("Integer"); } public static class SillyGenericWrapper { public  T get() { return null; } } } 

Salidas

 Object Integer 

La situación comienza a ser interesante cuando digamos que tiene 2 definiciones de métodos válidos que son elegibles para la sobrecarga. P.ej

 private static void print(Integer integer) { System.out.println("Integer"); } private static void print(String integer) { System.out.println("String"); } 

y ahora si invocas

 print(sillyGenericWrapper.get()); 

El comstackdor tendrá 2 definiciones de métodos válidas para elegir, por lo tanto, obtendrá un error de comstackción porque no puede dar preferencia a un método sobre el otro.

Lo ejecuté usando Java 1.8.0_40 y obtuve “Object”.

Si ejecutará el siguiente código:

 public class GenericTypeInference { private static final String fmt = "%24s: %s%n"; public static void main(String[] args) { print(new SillyGenericWrapper().get()); Method[] allMethods = SillyGenericWrapper.class.getDeclaredMethods(); for (Method m : allMethods) { System.out.format("%s%n", m.toGenericString()); System.out.format(fmt, "ReturnType", m.getReturnType()); System.out.format(fmt, "GenericReturnType", m.getGenericReturnType()); } private static void print(Object object) { System.out.println("Object"); } private static void print(String string) { System.out.println("String"); } public static class SillyGenericWrapper { public  T get() { return null; } } } 

Verás que obtienes:

Object public T com.xxx.GenericTypeInference $ SillyGenericWrapper.get () ReturnType: class java.lang.Object GenericReturnType: T

Lo que explica por qué se utiliza el método sobrecargado con Object y no el String.