son qué metodos manejo instanciar genericos genericas generic datos con comparar clases arreglos java generics java-8 language-lawyer

qué - metodos genericos java



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

Considere el siguiente programa:

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> T get() { return null; } } }

Imprime "String" en Java 8 y "Object" en Java 7.

Hubiera esperado que esto fuera una ambigüedad en Java 8, porque ambos métodos sobrecargados coinciden. ¿Por qué el compilador selecciona 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 tiempo de compilación. El código simplemente se comporta de manera diferente después de actualizar a Java 8.

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

ACTUALIZACIÓN: También he intentado compilar y ejecutar el ejemplo en Java 8 pero usando un nivel de lenguaje Java 7. El comportamiento era consistente con Java 7. Eso era lo esperado, pero aún sentía la necesidad de verificar.


En java7, las expresiones se interpretan de abajo hacia arriba (con muy pocas excepciones); El significado de una sub-expresión es "libre de contexto". Para una invocación de método, los tipos de argumentos se resuelven primero; el compilador 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ícita (como x->foo(x) ) en todas partes; los tipos de parámetros lambda no se especifican 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 argumento.

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 compilar los argumentos.

Ese es un cambio importante; y algunos códigos antiguos como el suyo serán víctimas de incompatibilidad.

Una solución alternativa es proporcionar un "tipeo de destino" al argumento con "contexto de conversión"

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

o, como la sugerencia de @ Holger, proporcione el parámetro de tipo <Object>get() para evitar inferencia por completo.

La sobrecarga del método 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 nombres diferentes.


En primer lugar, no tiene nada que ver con anular, pero tiene que ver con la sobrecarga.

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

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

Entonces al invocar

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

El compilador elige String versión de String sobre Object porque el método de print que toma String es más específico que el que toma Object . Si hubo Integer lugar de String , se seleccionará.

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

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> T get() { return null; } } }

Sale

Object Integer

La situación comienza a ser interesante cuando digamos que tiene 2 definiciones de métodos válidos que son elegibles para 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 compilador tendrá 2 definiciones de métodos válidos para elegir, por lo tanto, obtendrá un error de compilación porque no puede dar preferencia a un método sobre el otro.


Las reglas de inferencia de tipos han recibido una revisión importante en Java 8; más notablemente, la inferencia de tipos de destino ha mejorado mucho. Entonces, mientras que antes de Java 8 el sitio de argumento del método no recibió ninguna inferencia, por defecto a Object, en Java 8 se infiere el tipo aplicable más específico, en este caso String. JLS para Java 8 introdujo un nuevo capítulo Capítulo 18. Inferencia de tipo 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 compilador compiló con éxito el código que según JLS debería haber producido un error de ambigüedad ¿Por qué este método está sobrecargando de manera ambigua? Como Marco13 señala en los comentarios

Esta parte del JLS es probablemente la más complicada.

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

Como se muestra en el ejemplo de Java Tutoral ( inferencia de tipos )

Considere el siguiente método:

void processStringList(List<String> stringList) { // process stringList }

Suponga que desea invocar el método processStringList con una lista vacía. En Java SE 7, la siguiente declaración no se compila:

processStringList(Collections.emptyList());

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

List<Object> cannot be converted to List<String>

El compilador requiere un valor para el argumento de 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 es incompatible 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.<String>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.

Vale la pena mencionar esta incompatibilidad 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 primitivas ternarias condicionales y sin caja de Java 8

De acuerdo con JLS 15.12.2.5 Elección del método más específico :

Si más de un método 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 programació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 alguno de los siguientes es verdadero:

  1. m2 es genérico y se infiere 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 flexible, 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 aridad variable, y donde los primeros tipos de parámetros de aridad variable k de m1 son S1, ..., Sk y los primeros tipos de parámetros de aridad variable k 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 k + 1 parámetros, entonces el tipo de parámetro de aridad variable k + 1 ''de m1 es un subtipo del tipo de parámetro de aridad variable k + 1'' 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 las tres opciones coincide con nuestro caso. Como 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 es elegido por el compilador.


Lo ejecuté usando Java 1.8.0_40 y obtuve "Object".

Si ejecuta 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> T get() { return null; } } }

Verás que obtienes:

Objeto 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.