Почему ListListсписок> но не список> - PullRequest
6 голосов
/ 01 июля 2019

У меня общий интерфейс interface ListList<E> extends List<List<E>>. По некоторым причинам я не могу разыграть ListList<? super T> до List<List<? super T>>. Есть ли способ сделать это и почему это не работает?

К этому моменту я уже попробовал следующее:

  1. Простое назначение, таким образом мне удалось присвоить ListList<? super T> для List<? extends List<? super T>> (1), но когда я пытаюсь присвоить ListList<? super T> для List<List<? super T>>, я получаю Incompatible types ошибку времени компиляции (1.1) .
  2. Явное преобразование типов, оно не работает из-за одной и той же Incompatible types ошибки времени компиляции (2).
  3. Приведение к необработанному типу ListList, работает (3), но мне не нравятся необработанные типы.
  4. Добавление всех элементов от ListList<? super T> до List<? extends List<? super T>>, это работает (4), но мне нужно более общее решение, которое работает не только с ListList<E>, но и с любым универсальным типом.

Вот мой код:

ListList<? super T> var = new ArrayListList<>();
List<? extends List<? super T>> work = var; // (1)
List<List<? super T>> notWork = var; // (1.1)
List<List<? super T>> explicit = (List<List<? super T>>) var; // (2)
List<List<? super T>> raw = (ListList) var; // (3)
List<List<? super T>> copy = new ArrayList<>(); // (4)
copy.addAll(var); // (4)

Я ожидал, что ListList<? super T> будет List<List<? super T>>, но оказалось, что List<? extends List<? super T>>. Мне нужно знать, почему это так и как я могу привести его к List<List<? super T>> без необработанных типов и копирования элементов.

1 Ответ

2 голосов
/ 02 июля 2019

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

...