Списки с подстановочными знаками вызывают общую ошибку voodoo
Кто-нибудь знает, почему следующий код не компилируется? Ни 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);
Ответы
Ответ 1
(я предполагаю, что Bar
и Baz
оба являются подтипами Foo
.)
List<? extends Foo>
означает список элементов некоторого типа, который является подтипом Foo, но мы не знаем, какой тип. Примерами таких списков могут быть ArrayList<Foo>
, a LinkedList<Bar>
и a ArrayList<Baz>
.
Поскольку мы не знаем, какой подтип является параметром типа, мы не можем помещать в него объекты Foo
, а не объекты Bar
или Baz
. Но мы все же теперь, что параметр type является подтипом Foo
, поэтому каждый элемент, который уже есть в списке (и который мы можем получить из списка), должен быть объектом Foo
, поэтому мы можем использовать Foo f = list.get(0);
и аналогичные вещи.
Такой список может быть использован только для элементов из списка, а не для добавления элементов вообще (кроме null
, но я не знаю, разрешает ли это компилятор).
A List<Foo>
с другой стороны позволяет добавлять любой объект, который является объектом Foo
, а в качестве Bar
и Baz
являются подтипами Foo
, все объекты Bar
и Baz
являются Foo
, поэтому они также могут быть добавлены.
Ответ 2
Помните PECS: продюсер продюсеров, потребительский супер.
Поскольку вы пытаетесь добавить элементы в list2, это потребитель и не может быть объявлен как List<? extends Foo>
. Но тогда вы используете list2 как продюсер, когда добавляете его в list1. Поэтому list2 является как производителем, так и потребителем и должен быть List<Foo>
.
list1, как чистый потребитель, может быть List<? super Foo>
.
Ответ 3
Там ошибки. Позволяет изменить ваш код, учитывая, что 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 в списке2 будут добавлены в список1, который содержит только экземпляры Bar. Это объясняет вторую ошибку.
Ответ 4
Позвольте мне попытаться объяснить, в каком случае вам может понадобиться использовать <? 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 и некоторые Dads:
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) коллекцию. Для получения дополнительной информации вы можете прочитать о конкретизации подстановочных знаков и PECS в Java
Ответ 5
Извините, может быть, я неправильно понял ваш вопрос, но допустил:
public class Bar extends Foo{ }
этот код:
List<Foo> list2 = new ArrayList<Foo>()
list2.add( new Bar() );
не генерируют никаких ошибок для меня.
Итак, удаление wild card позволяет добавлять подклассы Foo.