Ответ 1
Главное понять общие типы - это то, что они не ковариантны.
Итак, пока вы можете это сделать:
final String string = "string";
final Object object = string;
Следующие команды не будут компилироваться:
final List<String> strings = ...
final List<Object> objects = strings;
Это необходимо, чтобы избежать ситуаций, когда вы обходите общие типы:
final List<String> strings = ...
final List<Object> objects = strings;
objects.add(1);
final String string = strings.get(0); <-- oops
Итак, рассмотрим примеры один за другим
1
Ваш общий метод принимает List<T>
, вы передаете List<?>
; который (по существу) a List<Object>
. T
может быть присвоен типу Object
, и компилятор счастлив.
2
Ваш общий метод тот же, вы передаете List<List<?>>
. T
может быть присвоен типу List<?>
, и компилятор снова счастлив.
3
Это в основном то же самое, что 2 с другим уровнем вложенности. T
по-прежнему является типом List<?>
.
4
Вот где он выглядит немного грушевидно, и там, где приходит моя точка сверху.
Ваш общий метод принимает List<List<T>>
. Вы передаете List<List<?>>
. Теперь, поскольку общие типы не ковариантны, List<?>
не может быть присвоен List<T>
.
Фактическая ошибка компилятора (Java 8):
требуется:
java.util.List<java.util.List<T>>
найдено:java.util.List<java.util.List<?>>
причина: не может вывести type-variable (s)T
(несоответствие аргументов,java.util.List<java.util.List<?>>
не может быть преобразовано вjava.util.List<java.util.List<T>>
)
В основном компилятор сообщает вам, что он не может найти T
для назначения из-за необходимости вывести тип List<T>
, вложенный во внешний список.
Давайте рассмотрим это чуть подробнее:
List<?>
является List
неизвестного типа - он может быть List<Integer>
или List<String>
; мы можем get
от него как Object
, , но мы не можем add
. Потому что в противном случае мы сталкиваемся с проблемой ковариации, о которой я упоминал.
List<List<?>>
является List
of List
неизвестного типа - он может быть List<List<Integer>>
или List<List<String>>
. В случае 1 можно было назначить T
Object
и просто не разрешать операции add
в списке подстановочных знаков. В случае 4 это невозможно сделать - прежде всего потому, что нет конструкции generics для предотвращения add
внешнего List
.
Если компилятор должен был назначить T
- Object
во втором случае, то возможно следующее:
final List<List<Integer>> list = ...
final List<List<?>> wildcard = list;
wildcard.add(Arrays.asList("oops"));
Таким образом, из-за ковариации невозможно присвоить a List<List<Integer>>
любому другому родовому List
безопасно.