В чем разница между <? расширяет Foo> и <Foo>

20

Кажется, у меня неправильное понимание разницы между <Foo>и <? extends Foo>. Из моего понимания, если бы мы имели

ArrayList<Foo> foos = new ArrayList<>();

Это указывает на то, что объекты этого типа Fooмогут быть добавлены в этот список массивов. Поскольку подклассы Fooтакже имеют тип Foo, они также могут быть добавлены без ошибок, как показано

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

где Bar extends Foo.

Теперь, скажем, я определил foosкак

ArrayList<? extends Foo> foos = new ArrayList<>();

Мое текущее понимание состоит в том, что это выражает some unknown type that extends Foo. Я понимаю, что это означает, что любые объекты, которые являются подклассом, Fooмогут быть добавлены в этот список; Это означает, что нет разницы между ArrayList<Foo>и ArrayList<? extends Foo>.

Чтобы проверить это, я попытался написать следующий код

ArrayList<? extends Foo> subFoos = new ArrayList<>();
subFoos.add(new Foo());
subFoos.add(new Bar());

но было предложено со следующей ошибкой компиляции

no suitable method found for add(Foo)
method java.util.Collection.add(capture#1 of ? extends Foo) is not applicable
(argument mismatch; Foo cannot be converted to capture#1 of ? extends Foo)

no suitable method found for add(Bar)
method java.util.Collection.add(capture#2 of ? extends Bar) is not applicable
(argument mismatch; Bar cannot be converted to capture#2 of ? extends Bar)

Исходя из моего нынешнего понимания, я понимаю, почему я не могу добавить a Fooв список <? extends Foo>, потому что он не является подклассом сам по себе; но мне любопытно, почему я не могу добавить Barв список.

Где дыра в моем понимании?

Zymus
источник
1
<? extends Foo>является конкретным и неизвестным классом, который расширяется Foo. Операция с этим классом допустима только в том случае, если она допустима для любого подкласса Foo.
Ордус
3
Вау. Чем больше вы узнаете о дженериках Java, тем больше у вас шумихи.
Мейсон Уилер

Ответы:

15

Как вы обнаружили сами, ArrayListзаявленное как ArrayList<? extends Foo> subFoos = new ArrayList<>();не будет очень полезным.

Чтобы увидеть полезность <? extends T>рассмотрим это:

List<Foo> collect( List<? extends Foo> a1, List<? extends Foo> a2 )
{
    List<Foo> collected = new ArrayList<>();
    collected.addAll( a1 );
    collected.addAll( a2 );
    return collected;
}

который позже можно использовать следующим образом:

List<Foo> foos = collect( new ArrayList<Foo>(), new ArrayList<Bar>() );

или следующим образом:

List<Foo> foos = collect( new ArrayList<Bar>(), new ArrayList<Foo>() );

обратите внимание, что ничего из вышеперечисленного не сработало бы, если бы collectметод был объявлен следующим образом:

List<Foo> collect( List<Foo> a1, List<Foo> a2 )
Майк Накис
источник