Понимание подстановочных знаков в 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.

Так как вы не знаете, к какому типу списка набирается, вы можете читать только из коллекции, и вы можете рассматривать только объекты, считанные как экземпляр объекта.

Пожалуйста, перейдите к границе супер-символа, если вы хотите вставить элементы в общую коллекцию подстановочных знаков.