Список <Список <? >> не может быть назначен List <List <? >> при использовании параметра типа

В следующем коде вызывается get(), и он присваивается переменной, тип которой List<List<?>>. get() возвращает a List<List<T>> и вызывается в экземпляре, для параметра типа T установлено значение ?, поэтому оно должно соответствовать.

import java.util.List;

class Test {
    void foo(NestedListProducer<?> test) {
        List<List<?>> a = test.get();
    }

    interface NestedListProducer<T> {
        List<List<T>> get();
    }
}

Но и IntelliJ IDEA, и Oracle javac версия 1.7.0_45 отклоняют мой код как недействительный. Это сообщение об ошибке "javac":

java: incompatible types
  required: java.util.List<java.util.List<?>>
  found:    java.util.List<java.util.List<capture#1 of ?>>

Почему этот код недействителен, то есть что может пойти не так, если это разрешено?

Ответ 1

List<List<T>> означает список, который вы можете прочитать List<T> или записать новый List<T> to, и аналогичным образом List<List<?>> означает список, который вы можете прочитать List<?> или записать новый List<T> to. То, что странно о ?, состоит в том, что вы можете преобразовать список любого типа S в List<?>. Например, вы можете написать:

void foo(List<String> a, List<Integer> b, List<List<?>> out) {
  List<?> unknownA = a;
  List<?> unknownB = b;
  out.add(a);
  out.add(b);
}

Если вы можете преобразовать List<List<T>> в List<List<?>>, вы можете вызвать foo с помощью List<List<PeanutButter>>, а затем добавить в него списки строк и целых чисел.

Обычно люди сталкиваются с этим, потому что они пытаются выразить мнение о том, что им нужна коллекция подкомпонов, типы которых не имеют значения. Если вы хотите, вы можете изменить тип от List<List<?>> до List<? extends List<?>>, который выражает понятие списка подписок, из которого я могу читать, но не писать. Разрешено преобразовывать a List<List<T>> в List<? extends List<?>>.

Ответ 2

? - это подстановочный знак, означающий любой тип. Один ? не может быть таким же, как другой ?, потому что другим ? может быть любой другой тип, и они не совпадают. Вы должны использовать generics, чтобы сказать, что типы одинаковы:

// Make this generic
<A> void foo(NestedListProducer<A> test) {
    List<List<A>> a = test.get();
}

Ответ 3

Вы, кажется, путаетесь с тем, как компилятор рассматривает List<?> и a List<List<?>>. A List<?> является List некоторого неизвестного типа, тогда как List<List<?>> является List of List (неизвестных неизвестных типов).

Итак, для List<?> подстановочный знак ? представляет собой один неизвестный тип, поэтому он будет захвачен компилятором держателем места для этого неизвестного типа. В List<List<?>> подстановочный знак ? представляет разные неизвестные типы. Компилятор не будет захватывать такие типы, поскольку не может быть одного владельца места для разных неизвестных типов.

Теперь рассмотрим ваш оригинальный пример:

void foo(NestedListProducer<?> test) {
    List<List<?>> a = test.get();
}

В этом случае компилятор захватит ? из NestedListProducer, чтобы создать параметр анонимного типа во время компиляции, и создаст вспомогательный метод, похожий на:

<CAP#1 of ?> void foo_2(NestedListProducer<CAP#1 of ?> test) {
    List<List<?>> a = test.get();
}

(Примечание: он не будет записывать ? в List<List<?>>, поэтому он останется таким, какой он есть).

Теперь тип возврата test.get() в этом случае будет List<List<CAP#1 of ?>>. Что не является захватом захвата, конвертируемым из List<List<?>>, и, следовательно, ему нельзя назначить его. Таким образом, он не компилируется.

Итак, обходным путем является добавление параметров типа самостоятельно, как уже было предложено:

<T> void foo(NestedListProducer<T> test) {
    List<List<T>> a = test.get();
}

Запрос из комментариев:

Теперь, когда вы спросили в комментариях, почему работает следующий код?

void foo(List<List<?>> arg) { 
    List<List<?>> a = arg; 
}

Из приведенного выше объяснения вы можете догадаться, что шаблон ? в List<List<?>> в формальном параметре не будет захвачен. Следовательно, назначение действительно от List<List<?>> до List<List<?>>, что действительно. Здесь нет CAP#1 of ?.

Ответ 4

Вы можете использовать этот трюк (подстановочный знак)

void foo(NestedListProducer<?> test) {
    foo2(test);
}

<T> void foo2(NestedListProducer<T> test) {
    List<List<T>> a = test.get();
}