Vários curingas em métodos genéricos deixam o compilador Java (e eu!) Muito confuso

Vamos primeiro considerar um cenário simples (veja a fonte completa em 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!
    }
}

Os dois curingas não são relacionados, e é por isso que você pode ligardoNothing com umList<String> e umList<Integer>. Em outras palavras, os dois? pode se referir a tipos totalmente diferentes. Portanto, o seguinte não é compilado, o que é esperado (também em 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 ?>)
    }
}

Até aí tudo bem, mas é aqui que as coisas começam a ficar muito confusas (como visto em ideone.com):

import java.util.*;

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

O código acima é compilado para mim no Eclipse e emsun-jdk-1.6.0.17 em ideone.com, mas deveria? Não é possível que tenhamos umList<List<Integer>> lol e umList<String> list, as duas situações análogos curingas não relacionadas deTwoListsOfUnknowns?

De fato, a leve modificação a seguir nessa direção não é compilada, o que é esperado (como visto em 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 ?>)
    }
}

Portanto, parece que o compilador está fazendo seu trabalho, mas obtemos isso (como visto em 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???
    }
}

Novamente, podemos ter p. umaList<List<Integer>> lol e umList<Float> list, então isso não deve compilar, certo?

Na verdade, vamos voltar ao mais simplesLOLUnknowns1 (dois curingas ilimitados) e tente ver se podemos de fato invocarprobablyIllegal de qualquer maneira. Vamos tentar primeiro o caso "fácil" e escolher o mesmo tipo para os dois curingas (como visto em 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>)
    }
}

Isso não faz sentido! Aqui nem estamos tentando usar dois tipos diferentes, e não compila! Tornando-o umList<List<Integer>> lol eList<String> list também dá um erro de compilação semelhante! De fato, pelas minhas experiências, a única maneira que o código compila é se o primeiro argumento for explícitonull tipo (como visto em 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
    }
}

Portanto, as perguntas são, com relação aLOLUnknowns1, LOLUnknowns1a eLOLUnknowns1b:

Que tipos de argumentos fazprobablyIllegal aceitar?Devemoslol.add(list); compilar? É typesafe?Isso é um bug do compilador ou estou entendendo mal as regras de conversão de captura para curingas?Apêndice A: LOL duplo?

Caso alguém esteja curioso, isso compila bem (como visto em 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: Curingas aninhados - o que eles realmente significam ???

Investigações adicionais indicam que talvez vários curingas não tenham nada a ver com o problema, mas sim umaaninhado curinga é a fonte da confusão.

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<?>>
    }
}

Portanto, parece talvez umList<List<String>> não é umList<List<?>>. De fato, enquanto qualquerList<E> é umList<?>, não se parece com nenhumList<List<E>> é umList<List<?>> (como visto em 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 então uma nova questão: exatamente o que é umList<List<?>>?

questionAnswers(3)

yourAnswerToTheQuestion