que programacion maquina interprete funciona ejemplos como caracteristicas java generics wildcard compiler-errors

programacion - Múltiples comodines en un método genérico hace que el compilador de Java(¡y yo!) Esté muy confundido



que es jvm en programacion (3)

Primero consideremos un escenario simple ( vea la fuente completa en ideone.com ):

import java.util.*; public class TwoListsOfUnknowns { static void doNothing(List<?> list1, List<?> list2) { } public static void main(String[] args) { List<String> list1 = null; List<Integer> list2 = null; doNothing(list1, list2); // compiles fine! } }

Los dos comodines no están relacionados, por lo que puede llamar a doNothing con una List<String> y una List<Integer> . En otras palabras, ¿los dos ? puede referirse a tipos completamente diferentes. Por lo tanto, no se compila lo siguiente, como es de esperar ( también en ideone.com ):

import java.util.*; public class TwoListsOfUnknowns2 { static void doSomethingIllegal(List<?> list1, List<?> list2) { list1.addAll(list2); // DOES NOT COMPILE!!! // The method addAll(Collection<? extends capture#1-of ?>) // in the type List<capture#1-of ?> is not applicable for // the arguments (List<capture#2-of ?>) } }

Hasta aquí todo bien, pero aquí es donde las cosas empiezan a ser muy confusas ( como se ve en ideone.com ):

import java.util.*; public class LOLUnknowns1 { static void probablyIllegal(List<List<?>> lol, List<?> list) { lol.add(list); // this compiles!! how come??? } }

El código anterior compila para mí en Eclipse y en sun-jdk-1.6.0.17 en ideone.com, pero ¿o sí? ¿No es posible que tengamos una List<List<Integer>> lol y una List<String> list , las dos situaciones análogas de wildcards no relacionadas de TwoListsOfUnknowns ?

De hecho, la siguiente ligera modificación hacia esa dirección no se compila, como es de esperar ( como se ve en ideone.com ):

import java.util.*; public class LOLUnknowns2 { static void rightfullyIllegal( List<List<? extends Number>> lol, List<?> list) { lol.add(list); // DOES NOT COMPILE! As expected!!! // The method add(List<? extends Number>) in the type // List<List<? extends Number>> is not applicable for // the arguments (List<capture#1-of ?>) } }

Entonces parece que el compilador está haciendo su trabajo, pero luego obtenemos esto ( como se ve en ideone.com ):

import java.util.*; public class LOLUnknowns3 { static void probablyIllegalAgain( List<List<? extends Number>> lol, List<? extends Number> list) { lol.add(list); // compiles fine!!! how come??? } }

De nuevo, podemos tener, por ejemplo, una List<List<Integer>> lol y una List<Float> list , por lo que no debería compilarse, ¿no?

De hecho, volvamos al LOLUnknowns1 (dos comodines ilimitados) más simple e intentamos ver si de hecho podemos invocar probablyIllegal Ilegal de alguna manera. Primero probemos el caso "fácil" y elijamos el mismo tipo para los dos comodines ( como se ve en ideone.com ):

import java.util.*; public class LOLUnknowns1a { static void probablyIllegal(List<List<?>> lol, List<?> list) { lol.add(list); // this compiles!! how come??? } public static void main(String[] args) { List<List<String>> lol = null; List<String> list = null; probablyIllegal(lol, list); // DOES NOT COMPILE!! // The method probablyIllegal(List<List<?>>, List<?>) // in the type LOLUnknowns1a is not applicable for the // arguments (List<List<String>>, List<String>) } }

¡Esto no tiene sentido! ¡Aquí ni siquiera estamos tratando de usar dos tipos diferentes, y no compila! Hacerlo una List<List<Integer>> lol y List<String> list también da un error de compilación similar! De hecho, de mi experimentación, la única forma en que el código se compila es si el primer argumento es un tipo null explícito ( como se ve en ideone.com ):

import java.util.*; public class LOLUnknowns1b { static void probablyIllegal(List<List<?>> lol, List<?> list) { lol.add(list); // this compiles!! how come??? } public static void main(String[] args) { List<String> list = null; probablyIllegal(null, list); // compiles fine! // throws NullPointerException at run-time } }

Entonces las preguntas son, con respecto a LOLUnknowns1 , LOLUnknowns1a y LOLUnknowns1b :

  • ¿Qué tipos de argumentos acepta probablyIllegal Illegal?
  • Debería lol.add(list); compilar en absoluto? ¿Es seguro?
  • ¿Es esto un error del compilador o estoy malinterpretando las reglas de conversión de captura para comodines?

Apéndice A: ¿Doble jaja?

En caso de que alguien tenga curiosidad, esto compila bien ( como se ve en ideone.com ):

import java.util.*; public class DoubleLOL { static void omg2xLOL(List<List<?>> lol1, List<List<?>> lol2) { // compiles just fine!!! lol1.addAll(lol2); lol2.addAll(lol1); } }

Apéndice B: comodines anidados: ¿qué significan realmente?

Investigaciones adicionales indican que quizás varios comodines no tienen nada que ver con el problema, sino que un comodín anidado es la fuente de la confusión.

import java.util.*; public class IntoTheWild { public static void main(String[] args) { List<?> list = new ArrayList<String>(); // compiles fine! List<List<?>> lol = new ArrayList<List<String>>(); // DOES NOT COMPILE!!! // Type mismatch: cannot convert from // ArrayList<List<String>> to List<List<?>> } }

Entonces parece que una List<List<String>> no es una List<List<?>> . De hecho, aunque cualquier List<E> es una List<?> , No parece que ninguna List<List<E>> sea ​​una List<List<?>> ( como se ve en ideone.com ):

import java.util.*; public class IntoTheWild2 { static <E> List<?> makeItWild(List<E> list) { return list; // compiles fine! } static <E> List<List<?>> makeItWildLOL(List<List<E>> lol) { return lol; // DOES NOT COMPILE!!! // Type mismatch: cannot convert from // List<List<E>> to List<List<?>> } }

Surge una nueva pregunta, entonces: ¿qué es una List<List<?>> ?


Como lo indica el Apéndice B, esto no tiene nada que ver con varios comodines, sino más bien, sin entender lo que realmente significa List<List<?>> .

Primero recordemos qué significa que los genéricos de Java son invariables:

  1. Un número Integer es un Number
  2. Una List<Integer> NO es una List<Number>
  3. Una List<Integer> IS a List<? extends Number> List<? extends Number>

Ahora simplemente aplicamos el mismo argumento a nuestra situación de lista anidada (ver el apéndice para más detalles) :

  1. Una List<String> es (capturable por) una List<?>
  2. Una List<List<String>> NO es (capturable por) a List<List<?>>
  3. Una List<List<String>> IS (capturable por) a List<? extends List<?>> List<? extends List<?>>

Con este entendimiento, se pueden explicar todos los fragmentos en la pregunta. La confusión surge al creer (falsamente) que un tipo como List<List<?>> puede capturar tipos como List<List<String>> , List<List<Integer>> , etc. Esto NO es verdadero.

Es decir, una List<List<?>> :

  • NO es una lista cuyos elementos son listas de algún tipo desconocido.
    • ... eso sería una List<? extends List<?>> List<? extends List<?>>
  • En cambio, es una lista cuyos elementos son listas de CUALQUIER tipo.

Fragmentos

Aquí hay un fragmento para ilustrar los puntos anteriores:

List<List<?>> lolAny = new ArrayList<List<?>>(); lolAny.add(new ArrayList<Integer>()); lolAny.add(new ArrayList<String>()); // lolAny = new ArrayList<List<String>>(); // DOES NOT COMPILE!! List<? extends List<?>> lolSome; lolSome = new ArrayList<List<String>>(); lolSome = new ArrayList<List<Integer>>();

Más fragmentos

Aquí hay otro ejemplo con un comodín anidado limitado:

List<List<? extends Number>> lolAnyNum = new ArrayList<List<? extends Number>>(); lolAnyNum.add(new ArrayList<Integer>()); lolAnyNum.add(new ArrayList<Float>()); // lolAnyNum.add(new ArrayList<String>()); // DOES NOT COMPILE!! // lolAnyNum = new ArrayList<List<Integer>>(); // DOES NOT COMPILE!! List<? extends List<? extends Number>> lolSomeNum; lolSomeNum = new ArrayList<List<Integer>>(); lolSomeNum = new ArrayList<List<Float>>(); // lolSomeNum = new ArrayList<List<String>>(); // DOES NOT COMPILE!!

Volver a la pregunta

Para volver a los fragmentos en la pregunta, el siguiente comportamiento se comporta como se esperaba ( como se ve en ideone.com ):

public class LOLUnknowns1d { static void nowDefinitelyIllegal(List<? extends List<?>> lol, List<?> list) { lol.add(list); // DOES NOT COMPILE!!! // The method add(capture#1-of ? extends List<?>) in the // type List<capture#1-of ? extends List<?>> is not // applicable for the arguments (List<capture#3-of ?>) } public static void main(String[] args) { List<Object> list = null; List<List<String>> lolString = null; List<List<Integer>> lolInteger = null; // these casts are valid nowDefinitelyIllegal(lolString, list); nowDefinitelyIllegal(lolInteger, list); } }

lol.add(list); es ilegal porque podemos tener una List<List<String>> lol y una List<Object> list . De hecho, si comentamos la declaración ofensiva, el código compila y eso es exactamente lo que tenemos con la primera invocación en main .

Todos los métodos ilegales probablyIllegal en la pregunta no son ilegales. Todos son perfectamente legales y seguros. No hay absolutamente ningún error en el compilador. Está haciendo exactamente lo que se supone que debe hacer.

Referencias

Preguntas relacionadas

Apéndice: las reglas de conversión de captura

(Esto se mencionó en la primera revisión de la respuesta, es un complemento digno del argumento de tipo invariante).

5.1.10 Conversión de captura

Deje que G nombre una declaración de tipo genérico con n parámetros de tipo formales A 1 ... A n con los límites correspondientes U 1 ... U n . Existe una conversión de captura de G <T 1 ... T n > a G <S 1 ... S n > , donde, para 1 <= i <= n :

  1. Si T i es un argumento de tipo comodín de la forma ? entonces …
  2. Si T i es un argumento de tipo comodín de la forma ? extends ? extends B i , entonces ...
  3. Si T i es un argumento de tipo comodín de la forma ? super ? super B i , entonces ...
  4. De lo contrario, S i = T i .

La conversión de captura no se aplica recursivamente.

Esta sección puede ser confusa, especialmente con respecto a la aplicación no recursiva de la conversión de captura (en adelante, CC ), pero la clave es que no todos ? puede CC; depende de dónde aparezca . No existe una aplicación recursiva en la regla 4, pero cuando se aplican las reglas 2 o 3, entonces la B i respectiva puede ser el resultado de una CC.

Analicemos algunos ejemplos simples:

  • List<?> Puede CC List<String>
    • El ? puede CC por la regla 1
  • List<? extends Number> List<? extends Number> puede CC List<Integer>
    • El ? puede CC por la regla 2
    • Al aplicar la regla 2, B i es simplemente Number
  • List<? extends Number> List<? extends Number> no puede CC List<String>
    • El ? puede CC por la regla 2, pero se produce un error de tiempo de compilación debido a tipos incompatibles

Ahora intentemos anidar:

  • List<List<?>> no puede CC List<List<String>>
    • Se aplica la regla 4, y CC no es recursiva, ¿entonces ? NO se puede CC
  • List<? extends List<?>> List<? extends List<?>> puede CC List<List<String>>
    • El primero ? puede CC por la regla 2
    • Al aplicar la regla 2, B i ahora es una List<?> , Que puede CC List<String>
    • ? Ambos ? puede CC
  • List<? extends List<? extends Number>> List<? extends List<? extends Number>> puede List<List<Integer>> CC List<List<Integer>>
    • El primero ? puede CC por la regla 2
    • Al aplicar la regla 2, B i ahora es una List<? extends Number> List<? extends Number> , que puede CC List<Integer>
    • ? Ambos ? puede CC
  • List<? extends List<? extends Number>> List<? extends List<? extends Number>> no puede List<List<Integer>> CC List<List<Integer>>
    • El primero ? puede CC por la regla 2
    • Al aplicar la regla 2, B i ahora es una List<? extends Number> List<? extends Number> , que puede CC, pero da un error de tiempo de compilación cuando se aplica a List<Integer>
    • ? Ambos ? puede CC

Para ilustrar aún más por qué algunos ? CC y otros no pueden, considere la siguiente regla: NO puede crear instancias directas de un tipo de comodín. Es decir, lo siguiente da un error de tiempo de compilación:

// WildSnippet1 new HashMap<?,?>(); // DOES NOT COMPILE!!! new HashMap<List<?>, ?>(); // DOES NOT COMPILE!!! new HashMap<?, Set<?>>(); // DOES NOT COMPILE!!!

Sin embargo, lo siguiente compila muy bien:

// WildSnippet2 new HashMap<List<?>,Set<?>>(); // compiles fine! new HashMap<Map<?,?>, Map<?,Map<?,?>>>(); // compiles fine!

La razón por la cual WildSnippet2 compila es porque, como se explicó anteriormente, ¿ninguno de los ? puede CC. En WildSnippet1 , la K o la V (o ambas) de HashMap<K,V> pueden CC, lo que hace que la instanciación directa a través de new ilegal.


no es un experto, pero creo que puedo entenderlo.

cambiemos su ejemplo a algo equivalente, pero con más tipos distintivos:

static void probablyIllegal(List<Class<?>> x, Class<?> y) { x.add(y); // this compiles!! how come??? }

cambiemos la Lista a [] para que sea más esclarecedor:

static void probablyIllegal(Class<?>[] x, Class<?> y) { x.add(y); // this compiles!! how come??? }

ahora, x no es una matriz de algún tipo de clase. es una matriz de cualquier tipo de clase. puede contener una Class<String> y una Class<Int> . esto no se puede expresar con un parámetro de tipo ordinario:

static<T> void probablyIllegal(Class<T>[] x //homogeneous! not the same!

Class<?> Es un súper tipo de Class<T> para cualquier T Si pensamos que un tipo es un conjunto de objetos , establecemos que Class<?> Es la unión de todos los conjuntos de Class<T> para todos los T (¿Incluye itselft? No lo sé ...)


  • No se debe aceptar ningún argumento con genéricos . En el caso de LOLUnknowns1b el null se acepta como si el primer argumento se escribiera como List . Por ejemplo, esto sí compila:

    List lol = null; List<String> list = null; probablyIllegal(lol, list);

  • IMHO lol.add(list); ni siquiera debería compilar, pero como lol.add() necesita un argumento de tipo List<?> y como la lista cabe en List<?> , funciona.
    Un extraño ejemplo que me hace pensar en esta teoría es:

    static void probablyIllegalAgain(List<List<? extends Number>> lol, List<? extends Integer> list) { lol.add(list); // compiles fine!!! how come??? }

    lol.add() necesita un argumento de tipo List<? extends Number> List<? extends Number> y la lista se escribe como List<? extends Integer> List<? extends Integer> , encaja. No funcionará si no coincide. Lo mismo para el doble LOL y otros comodines anidados, siempre que la primera captura coincida con la segunda, todo está bien (y no debería ser).

  • De nuevo, no estoy seguro, pero realmente parece un error.

  • Me alegra no ser el único en usar variables de lol todo el tiempo.

Recursos:
http://www.angelikalanger.com , preguntas frecuentes sobre genéricos

EDITs:

  1. Se agregó un comentario sobre Double Lol
  2. Y comodines anidados.