Список <Список <? >> не может быть назначен 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();
}