Понимание подстановочных знаков в Java-дженериках
Я не уверен, почему последнее утверждение в следующем коде является незаконным. Integer
должен быть подтипом ?
, поэтому почему я не могу назначить его b
?
List<String> a = new ArrayList<String>();
a.add("foo");
// b is a List of anything
List<?> b = a;
// retrieve the first element
Object c = b.get(0);
// This is legal, because we can guarantee
// that the return type "?" is a subtype of Object
// Add an Integer to b.
b.add(new Integer (1));
Ответы
Ответ 1
Дело в том, что b
ссылается на список какого-то типа, но компилятор не знает, что такое тип, поэтому он не знает, действительно ли он добавляет к нему Integer
. И тоже хорошо, учитывая ваш пример - вы должны добавить Integer
к объекту, изначально созданному для хранения списка строк. Конечно, эта информация теряется во время выполнения в Java, но компилятор пытается сохранить вас как можно безопаснее.
Подробную информацию см. в часто задаваемых частотах Java.
Ответ 2
Integer
не является подтипом ?
(обязательно). ?
- подстановочный знак; вы должны интерпретировать его как значение "неизвестно".
Итак, List<?>
не совпадает с List<Object>
. Вы можете добавить что-нибудь, что вам нравится, в List<Object>
.
Ответ 3
Ссылка "b" объявляется как Список, то есть "Список чего-то, чего я еще не знаю".
Вы можете назначить практически любую реализацию для этой ссылки, например List. Вот почему в эту ссылку запрещается добавлять что-либо в списки.
Ответ 4
Это потому, что мы не можем гарантировать, что Integer является подтипом типа параметра "?".
Посмотрите:
Object c = b.get(0);
Это верно, как? всегда будет подтипом объекта.
Ответ 5
Грубое эмпирическое правило для коллекций и дженериков следующее:
-
Collection<Foo>
- это сборник, из которого вы можете получить Foo и к которому вы можете добавить Foo.
-
Collection<? extends Foo>
- это коллекция, из которой вы можете получить Foo, , но вы ничего не можете добавить.
Почему это так? Потому что, когда вы говорите Collection<Foo>
, вы обещаете для пользователей этой ссылки, чтобы они могли вызвать метод add(Foo elem)
для рассматриваемого объекта. С другой стороны, когда вы используете версию подстановочного знака, вы сохраняете "реальный" класс параметров секретом от пользователей ссылки - они знают, что любой элемент, который они извлекают из коллекции, можно передать в Foo, но не они могут добавить к нему любое Foo.
Почему это полезно? Потому что есть много, много и много случаев, когда вы будете писать методы, которые захотят итерации через коллекцию, чьи элементы - все Foos, но вам никогда не нужно добавлять какие-либо элементы. Так вот так:
public Foo findAFooThatILike(Collection<? extends Foo> foos);
Использование подстановочного знака здесь означает, что метод примет в качестве аргумента a Collection<Foo>
и набор любого подтипа Foo; например, если Bar является подтипом Foo, сигнатура выше означает, что вы можете передать Collection<Bar>
методу.
Если, с другой стороны, вы пишете подпись следующим образом:
public Foo findAFooThatILike(Collection<Foo> foos);
... тогда вы могли бы не передавать в качестве аргумента. Зачем? Поскольку для того, чтобы быть Collection<Foo>
, он должен поддерживать метод add(Foo elem)
, а Collection<Bar>
- нет.
Обратите внимание, что эти эмпирические правила применяются только к интерфейсам и классам коллекции. (Также обратите внимание, что Collection<? extends Foo>
не означает "только для чтения коллекции Foo"; многие методы удаления элементов из коллекции могут работать, когда вы не знаете точный тип элемента).
Итак, вернемся к исходному вопросу: List<?>
совпадает с List<? extends Object>
. Это список, из которого вы можете получить ссылки на экземпляры Object, но вы ничего не можете добавить.
Ответ 6
Вот краткий обзор того, что вы можете и не можете сделать с дженериками:
List<? extends Number> listOfAnyNumbers = null;
List<Number> listOfNumbers = null;
List<Integer> listOfIntegers = null;
listOfIntegers = listOfNumbers; // Error - because listOfNumbers may contain non-integers
listOfNumbers = listOfIntegers; // Error - because to a listOfNumbers you can add any Number, while to listOfIntegers you cannot.
listOfIntegers = listOfAnyNumbers; // Error - because listOfAnyNumbers may contain non-integers
listOfAnyNumbers = listOfIntegers; // OK - because listOfIntegers is a list of ?, where ? extends Number.
listOfNumbers = listOfAnyNumbers; // Error - because listOfAnyNumbers may actually be List<Float>, to which you cannot add any Number.
listOfAnyNumbers = listOfNumbers; // OK - because listOfNumbers is a list of ?, where ? extends Number.
Ответ 7
Список <? > означает список, набранный неизвестному типу. Это может быть класс Integer, String или XYZ.
Так как вы не знаете, к какому типу списка набирается, вы можете читать только из коллекции, и вы можете рассматривать только объекты, считанные как экземпляр объекта.
Пожалуйста, перейдите к границе супер-символа, если вы хотите вставить элементы в общую коллекцию подстановочных знаков.