Вначале похоже, что все эти назначения должны быть успешными, но они этого не делают из-за внутреннего подстановочного знака ? super T
. Если мы удалим эти шаблоны, то все назначения будут скомпилированы.
public static <T> void test() {
ListList<T> var = new ArrayListList<>();
List<? extends List<T>> work = var; // Compiles
List<List<T>> notWork = var; // Compiles
List<List<T>> explicit = (List<List<T>>) var; // Compiles
List<List<T>> raw = (ListList) var; // Compiles with warning
List<List<T>> copy = new ArrayList<>(); // Compiles
copy.addAll(var); // Compiles
}
Я все еще получаю предупреждение о непроверенном преобразовании для (3), но все они все еще компилируются.
На первый взгляд это выглядит как объявление интерфейса
ListList<E> extends List<List<E>>
делает ListList
эквивалентным List
из List
с. Однако, что вы сделали, это взяли параметр вложенного типа и сделали его параметром основного типа. Причина, по которой это имеет значение, заключается в том, что вложенные символы подстановки не выполняют захват символов подстановки .
Вложенный подстановочный знак здесь означает «список списков любого типа, соответствующего ограниченному», но подстановочный знак основного уровня здесь означает «список-список» определенного, но неизвестного типа соответствует границе ".
Нельзя добавить объект, который является супертипом нижней границы коллекции, поскольку параметр типа - определенный, но еще неизвестный тип - может быть фактической границей.
List<? super Integer> test2 = new ArrayList<>();
test2.add(2); // Compiles; can add 2 if type parameter is Integer, Number, or Object
test2.add((Number) 2); // Error - Can't add Number to what could be Integer
test2.add(new Object()); // Error - Can't add Object to what could be Integer
Поскольку обобщения Java являются инвариантными, типы должны точно совпадать, когда задействованы параметры типа, поэтому подобные случаи для ListList
все не компилируются.
// My assumption of how your ArrayListList is defined.
class ArrayListList<E> extends ArrayList<List<E>> implements ListList<E> {}
ListList<? super Integer> llOfSuperI = new ArrayListList<>();
llOfSuperI.add(new ArrayList<Integer>()); // capture fails to match Integer
llOfSuperI.add(new ArrayList<Number>()); // capture fails to match Number
llOfSuperI.add(new ArrayList<Object>()); // capture fails to match Object
Однако List
из List
компилируется со всеми 3 случаями.
List<List<? super Integer>> lOfLOfSuperI = new ArrayList<>();
lOfLOfSuperI.add(new ArrayList<Integer>()); // no capture; compiles
lOfLOfSuperI.add(new ArrayList<Number>()); // no capture; compiles
lOfLOfSuperI.add(new ArrayList<Object>()); // no capture; compiles
Ваш ListList
является типом, отличным от List
из List
s, но отличающееся поведение обобщенных элементов, в котором определен параметр типа, означает, что существует другое поведение обобщенных элементов. Вот почему вы не можете напрямую назначить ListList<? super T>
для List<List<? super T>>
(1.1), а также почему вы не можете разыграть его (2). Вы можете привести к необработанному типу, чтобы заставить его скомпилировать (3), но это открывает возможности ClassCastException
в будущем использовании приведенного объекта; вот о чем предупреждение. Вы можете назначить его на List<? extends List<? super T>>
(1), введя другой подстановочный знак для захвата отношения подтипа, но который вводит подстановочный знак, который будет захвачен; Вы не сможете добавить что-нибудь полезное в этот список.
Эти различия возникли только потому, что подстановочный знак вводит подстановочный знак и связанные с ним различия. Без использования подстановочного знака ListList<E>
эквивалентно List<List<E>>
и, как показано в верхней части этого ответа, не показывает проблем при компиляции кода.
Если вы хотите, чтобы во всех ваших подсписках использовался один и тот же параметр типа, продолжайте и используйте свой интерфейс ListList
, но не используйте подстановочные знаки. Это принудительно приводит к одинаковому параметру типа для всех списков, которые добавляются в ваш ListList
, то есть ListList<Integer>
может содержать только List<Integer>
s.
Если вы хотите, чтобы все ваши подсписки просто соответствовали шаблону, например, укажите List<Number>
, List<Integer>
и List<Object>
в одном списке, а затем просто используйте List<List<? super T>>
, чтобы избежать подстановочного знака.