Несколько подстановочных знаков в универсальных методах делают Java-компилятор (и меня!) Очень запутанным

Давайте сначала рассмотрим простой сценарий (см полный источник на 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!
    }
}

Два подстановочных знака не связаны, поэтому вы можете позвонитьdoNothing сList<String> иList<Integer>, Другими словами, два? могут относиться к совершенно разным типам. Следовательно, следующее не компилируется, чего и следовало ожидать (также на 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 ?>)
    }
}

Пока все хорошо, но вот тут все начинает очень запутанно (как видно на ideone.com):

import java.util.*;

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

Приведенный выше код компилируется для меня в Eclipse и наsun-jdk-1.6.0.17 в ideone.com, но так ли? Разве не возможно, что у нас естьList<List<Integer>> lol иList<String> list, аналогичные две несвязанные ситуации с подстановочными знаками изTwoListsOfUnknowns?

На самом деле следующая небольшая модификация в этом направлении не компилируется, чего и следовало ожидать (как видно на 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 ?>)
    }
}

Похоже, что компилятор делает свою работу, но тогда мы получаем это (как видно на 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???
    }
}

Опять же, мы можем иметь, например,List<List<Integer>> lol иList<Float> listтак что это не должно компилироваться, верно?

На самом деле, давайте вернемся к более простомуLOLUnknowns1 (два неограниченных подстановочных знака) и попытаться выяснить, можем ли мы на самом деле вызватьprobablyIllegal в любом случае. Давайте сначала попробуем «легкий» случай и выберем одинаковый тип для двух подстановочных знаков (как видно на 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>)
    }
}

Это не имеет никакого смысла! Здесь мы даже не пытаемся использовать два разных типа, и он не компилируется! Делая этоList<List<Integer>> lol а такжеList<String> list также дает похожую ошибку компиляции! Фактически, из моих экспериментов, единственный способ, которым код компилируется, это если первый аргумент является явнымnull тип (как видно на 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
    }
}

Таким образом, вопросы, касающиесяLOLUnknowns1, LOLUnknowns1a а такжеLOLUnknowns1b:

Какие типы аргументов делаетprobablyIllegal принимать?Долженlol.add(list); компилировать вообще? Это безопасно?Это ошибка компилятора или я неправильно понимаю правила преобразования захвата для подстановочных знаков?Приложение A: Двойной LOL?

В случае, если кому-то интересно, это хорошо компилируется (как видно на 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);
    }
}
Приложение B: Вложенные шаблоны - что они на самом деле означают ???

Дальнейшие исследования показывают, что, возможно, несколько подстановочных знаков не имеют ничего общего с проблемой, а скореевложенными Подстановочный знак является источником путаницы.

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

Так это выглядит, возможно,List<List<String>> это неList<List<?>>, На самом деле, пока любойList<E> этоList<?>это не похоже наList<List<E>> этоList<List<?>> (как видно на 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<?>>
    }
}

Тогда возникает новый вопрос: что же такоеList<List<?>>?

Ответы на вопрос(3)

Ваш ответ на вопрос