Múltiples comodines en métodos genéricos hacen que el compilador de Java (¡y yo!) Esté muy confundido

Consideremos primero un escenario simple (ver 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 eso puede llamardoNothing con unList<String> y unList<Integer>. En otras palabras, los dos? puede referirse a tipos completamente diferentes. Por lo tanto, lo siguiente no se compila, lo cual 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 ahora todo bien, pero aquí es donde las cosas comienzan 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 se compila para mí en Eclipse y ensun-jdk-1.6.0.17 en ideone.com, pero ¿debería? ¿No es posible que tengamos unList<List<Integer>> lol y unList<String> list, las dos situaciones comodines análogas deTwoListsOfUnknowns?

De hecho, la siguiente modificación leve hacia esa dirección no se compila, lo cual 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 ?>)
    }
}

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 p. unaList<List<Integer>> lol y unList<Float> list, así que esto no debería compilarse, ¿verdad?

De hecho, volvamos a lo más simpleLOLUnknowns1 (dos comodines ilimitados) e intente ver si de hecho podemos invocarprobablyIllegal de cualquier manera. Probemos primero con el caso "fácil" y elija 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 se compila! Haciéndolo unList<List<Integer>> lol yList<String> list ¡también da un error de compilación similar! De hecho, desde mi experimentación, la única forma en que se compila el código es si el primer argumento es explícitonull tipo (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 aLOLUnknowns1, LOLUnknowns1a yLOLUnknowns1b:

¿Qué tipos de argumentos hace?probablyIllegal ¿aceptar?Deberíalol.add(list); compilar en absoluto? ¿Es seguro para escribir?¿Es esto un error del compilador o no estoy entendiendo las reglas de conversión de captura para comodines?Apéndice A: Doble LOL?

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?

La investigación adicional indica que quizás varios comodines no tienen nada que ver con el problema, sino más bien unanidado El comodín 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 quizás unList<List<String>> no es unList<List<?>>. De hecho, mientras que cualquierList<E> es unList<?>, no se parece a ningunoList<List<E>> es unList<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<?>>
    }
}

Entonces surge una nueva pregunta: qué es unList<List<?>>?

Respuestas a la pregunta(3)

Su respuesta a la pregunta