Давайте начнем с простых списков. Integer является подклассом Number, поэтому вы можете назначить Integer на Number:
Integer i = 42;
Number n = i; // Ok
Теперь давайте получим List<Number>
. Разве мы не можем поставить туда List<Integer>
?
List<Number> numbers = new ArrayList<Integer>(); // Fails
Нет, не получится. Почему? Поскольку генерики в Java являются инвариантными, это означает, что для любых двух различных типов T1 и T2 ни List<T1>
не является подтипом List<T2>
, ни List<T2>
не является подтипом List<T1>
независимо от отношений между T1 и T2. Хорошо, это формально объясняет, почему вышеприведенное присваивание недействительно, но что за логика c стоит за этим?
List<Number>
должен иметь возможность хранить любое число, это означает, что если у нас есть объявление
List<Number> numbers;
тогда мы сможем сделать number.add(Integer(42))
, number.add(Double(Math.PI))
и любые другие number.add(subClassOfNumber)
. Однако если указанное выше назначение
List<Number> numbers = new ArrayList<Integer>(); // ?
будет действительным, то numbers
теперь будет содержать список, который может хранить только целые числа, поэтому numbers.add(anyNumberButInteger)
потерпел бы неудачу, что нарушает договор о List<Number>
.
Можно создать список, который будет содержать экземпляры некоторого подкласса Number:
List<? extends Number> list = Arrays.asList(5, 6, 7, 8); // Ok
Этот список можно прочитать без проблем:
System.out.println(list.get(2)); // 7
Однако вы не можете поместить туда ничего, кроме нуля, потому что невозможно знать точный тип элементов списка во время компиляции и, следовательно, невозможно выполнить проверку на безопасность:
List<? extends Number> list = ThreadLocalRandom.current().nextBoolean() ?
new ArrayList<Integer>() :
new ArrayList<Double>();
list.add(null); // Ok
list.add(Double.valueOf(3.1415)); // Fails, because we don't know if Double(3.1415) is actually compatible with elements of the list
Хорошо, теперь к вашему примеру:
List<List<?>> listOfLists = new ArrayList<List<String>>();
Чтобы это задание работало, вам нужно иметь слева List<? extends List<String>>
, но, поскольку Java generi c является инвариантом, единственный тип, который расширяет List<String>
, это сам List<String>
, поэтому единственной допустимой комбинацией является
List<List<String>> listOfListsOfStrings = new ArrayList<List<String>>();
. Можно создать переменную, представляющую список, которая состоит из некоторых списков, используя raw типы:
List<? extends List> listOfLists = new ArrayList<List<String>>(); // Compiles, but loses generics. DON'T DO THIS!
List<String> listOfStrings = listOfLists.get(0); // Compiles, even would work as expected assuming we populated listOfLists correctly
List<Integer> listOfIntegers = listOfLists.get(0); // Compiles, but does not work as expected (fail at runtime when accessing elements)
TLDR: единственный универсальный c класс, который супер / расширяется List<String>
равен List<String>
, поэтому List<some class which extends List<String>>
автоматически означает List<List<String>>
.