Подтвердить что ты не робот

Списки с подстановочными знаками вызывают общую ошибку 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);
4b9b3361

Ответ 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.