Jaka jest różnica między <? rozszerza Foo> i <Foo>

20

Wydaje mi się, że mam nieporozumienie na temat różnicy między <Foo>i <? extends Foo>. Z mojego zrozumienia, gdybyśmy mieli

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

Oznacza to, że Foodo tej listy tablic można dodać obiekty typu . Ponieważ podklasy Foosą również typu Foo, można je również dodawać bezbłędnie, jak pokazano za pomocą

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

gdzie Bar extends Foo.

Powiedzmy, że zdefiniowałem foosjako

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

Moje obecne rozumienie jest takie, że to wyraża some unknown type that extends Foo. Rozumiem, że oznacza to, że wszelkie obiekty, które są podklasą, Foomożna dodać do tej listy; co oznacza, że ​​nie ma różnicy między ArrayList<Foo>i ArrayList<? extends Foo>.

Aby to przetestować, próbowałem napisać następujący kod

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

ale został wyświetlony monit o następujący błąd kompilacji

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)

W oparciu o moje obecne rozumienie rozumiem, dlaczego mogę nie być w stanie dodać Foolisty do listy <? extends Foo>, ponieważ nie jest ona sama w sobie podklasą; ale jestem ciekawy, dlaczego nie mogę dodać Barlisty do listy.

Gdzie jest dziura w moim rozumieniu?

Zymus
źródło
1
<? extends Foo>jest specyficzną i nieznaną klasą, która się rozszerza Foo. Operacja z tą klasą jest legalna tylko wtedy, gdy byłaby legalna dla dowolnej podklasy Foo.
Zwyczajny
3
Łał. Im więcej dowiesz się o rodzajach języka Java, tym bardziej napotkany błąd.
Mason Wheeler,

Odpowiedzi:

15

Jak sam odkryłeś, ArrayListdeklaracja jako ArrayList<? extends Foo> subFoos = new ArrayList<>();nie byłaby bardzo przydatna.

Aby zobaczyć przydatność, <? extends T>zastanów się nad tym:

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

które można później wykorzystać w następujący sposób:

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

lub w następujący sposób:

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

zwróć uwagę, że żadne z powyższych nie zadziałałoby, gdyby collectmetoda została zadeklarowana w następujący sposób:

List<Foo> collect( List<Foo> a1, List<Foo> a2 )
Mike Nakis
źródło