Списки с подстановочными знаками вызывают общую ошибку вуду
Кто-нибудь знает, почему следующий код не компилируется? Ни add(), ни addAll() не работают должным образом. Удаление части "? Extends" заставляет все работать, но тогда я не смог бы добавить подклассы Foo.
List<? extends Foo> list1 = new ArrayList<Foo>();
List<? extends Foo> list2 = new ArrayList<Foo>();
/* Won't compile */
list2.add( new Foo() ); //error 1
list1.addAll(list2); //error 2
ошибка 1:
IntelliJ говорит:
add(capture<? extends Foo>) in List cannot be applied to add(Foo)
Компилятор говорит:
cannot find symbol
symbol : method addAll(java.util.List<capture#692 of ? extends Foo>)
location: interface java.util.List<capture#128 of ? extends Foo>
ошибка 2:
IntelliJ дает мне
addAll(java.util.Collection<? extends capture<? extends Foo>>) in List cannot be applied to addAll(java.util.List<capture<? extends Foo>>)
В то время как компилятор просто говорит
cannot find symbol
symbol : method addAll(java.util.List<capture#692 of ? extends Foo>)
location: interface java.util.List<capture#128 of ? extends Foo>
list1.addAll(list2);
5 ответов
(Я предполагаю, что здесь Bar
а также Baz
оба подтипа Foo
.)
List<? extends Foo>
означает список элементов некоторого типа, который является подтипом Foo, но мы не знаем, какой тип. Примеры таких списков будут ArrayList<Foo>
, LinkedList<Bar>
и ArrayList<Baz>
,
Поскольку мы не знаем, какой подтип является параметром типа, мы не можем поставить Foo
объекты в него, ни Bar
или же Baz
объекты. Но мы все еще знаем, что параметр типа является подтипом Foo
таким образом, каждый элемент уже в списке (и который мы можем получить из списка) должен быть Foo
объект, поэтому мы можем использовать Foo f = list.get(0);
и тому подобные вещи.
Такой список можно использовать только для удаления элементов из списка, а не для добавления элементов вообще (кроме null
, но я не знаю, если компилятор на самом деле позволяет это).
List<Foo>
с другой стороны, позволяет добавлять любой объект, который является Foo
объект - и как Bar
а также Baz
являются подтипами Foo
, все Bar
а также Baz
объекты Foo
объекты, так что они могут быть добавлены тоже.
Помните PECS: продюсер продлевает, потребительский супер.
Поскольку вы пытаетесь добавить элементы в список list2, он является потребителем и не может быть объявлен как List<? extends Foo>
, Но тогда вы используете list2 в качестве продюсера, когда добавляете его в list1. Следовательно, list2 является как производителем, так и потребителем и должен быть List<Foo>
,
list1, как чистый потребитель, может быть List<? super Foo>
,
Это ошибки. Давайте изменим ваш код, учитывая, что Bar
а также Baz
два разных типа расширяющихся Foo
:
List<? extends Foo> list1 = new ArrayList<Bar>();
List<? extends Foo> list2 = new ArrayList<Baz>();
Если list1.add(new Foo())
было разрешено, вы можете добавить экземпляры Foo в коллекцию, содержащую экземпляры Bar. Это объясняет первую ошибку.
Если list1.addAll(list2)
было разрешено, все экземпляры Baz в list2 будут добавлены в list1, который содержит только экземпляры Bar. Это объясняет вторую ошибку.
Позвольте мне объяснить, в каком случае вам может понадобиться <? extend Classname>
,
Итак, допустим, у вас есть 2 класса:
class Grand {
private String name;
public Grand(String name) {
this.setName(name);
}
public Grand() {
}
public void setName(String name) {
this.name = name;
}
}
class Dad extends Grand {
public Dad(String name) {
this.setName(name);
}
public Dad() {
}
}
Допустим, у вас есть 2 коллекции, каждая из которых содержит несколько Grands и несколько пап:
List<Dad> dads = new ArrayList<>();
dads.add(new Dad("Dad 1"));
dads.add(new Dad("Dad 2"));
dads.add(new Dad("Dad 3"));
List<Dad> grands = new ArrayList<>();
dads.add(new Dad("Grandpa 1"));
dads.add(new Dad("Grandpa 2"));
dads.add(new Dad("Grandpa 3"));
Теперь давайте предположим, что мы хотим иметь коллекцию, которая будет содержать объекты Grand или Dad:
List<Grand> resultList;
resultList = dads; // Error - Incompatable types List<Grand> List<Dad>
resultList = grands;//Works fine
Как мы можем избежать этого? Просто используйте подстановочный знак:
List<? extends Grand> resultList;
resultList = dads; // Works fine
resultList = grands;//Works fine
Обратите внимание, что вы не можете добавлять новые элементы в такую коллекцию (resultList). Для получения дополнительной информации вы можете прочитать о Wildcard и PECS-концепции в Java
Извините, может быть, я неправильно понял ваш вопрос, но предпочел:
public class Bar extends Foo{ }
этот код:
List<Foo> list2 = new ArrayList<Foo>()
list2.add( new Bar() );
не генерируйте никаких ошибок для меня.
Таким образом, удаление группового символа позволяет добавлять подклассы Foo.