Списки с подстановочными знаками вызывают общую ошибку вуду - PullRequest
34 голосов
/ 23 марта 2011

Кто-нибудь знает, почему следующий код не компилируется?Ни 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 ]

52 голосов
/ 23 марта 2011

(я предполагаю, что Bar и Baz являются подтипами Foo.)

List<? extends Foo> означает список элементов некоторого типа, который является подтипомФу, но мы не знаем, какой тип .Примерами таких списков могут быть ArrayList<Foo>, LinkedList<Bar> и ArrayList<Baz>.

Поскольку мы не знаем, какой подтип является параметром типа, мы не можем поместить объекты Foo вэто ни Bar, ни Baz объекты.Но мы все еще знаем, что параметр типа является подтипом Foo, поэтому каждый элемент, уже находящийся в списке (и который мы можем получить из списка), должен быть объектом Foo, поэтому мы можем использовать Foo f = list.get(0); и аналогичныевещи.

Такой список можно использовать только для удаления элементов из списка, а не для добавления элементов вообще (кроме null, но я не знаю, позволяет ли это на самом деле компилятор).

A List<Foo>, с другой стороны, позволяет добавлять любой объект, который является Foo объектом, а Bar и Baz являются подтипами Foo, все Bar и Bazобъекты являются Foo объектами, поэтому их также можно добавлять.

24 голосов
/ 23 марта 2011

Помните, что PECS: Производитель расширяется, потребительский супер .

Поскольку вы пытаетесь добавить элементы в список list2, он является потребителем и не может быть объявлен как List<? extends Foo>. Но тогда вы используете list2 в качестве продюсера, когда добавляете его в list1. Следовательно, list2 является как производителем, так и потребителем и должен быть List<Foo>.

list1, как чистый потребитель, может быть List<? super Foo>.

8 голосов
/ 23 марта 2011

Это ошибки. Давайте изменим ваш код, учитывая, что 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 Это объясняет вторую ошибку.

4 голосов
/ 02 февраля 2017

Позвольте мне объяснить, в каком случае вам может понадобиться <? 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 коллекции, каждая из которых содержит несколько грандов и несколько пап:

    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

2 голосов
/ 23 марта 2011

Извините, может быть, я неправильно понял ваш вопрос, но предпочел:

public class Bar extends Foo{ }

этот код:

List<Foo> list2 = new ArrayList<Foo>()
list2.add( new Bar() );

не генерируйте никаких ошибок для меня.

Итак, удаление символа подстановки позволяет добавлять подклассы Foo.

...