Java Generics – Difference Between and

genericsjava

I seem to have a misunderstanding about the difference between <Foo> and <? extends Foo>. From my understanding, if we had

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

This indicates that objects of type Foo can be added to this array list. Since subclasses of Foo are also of type Foo, they can also be added without an error, as illustrated by

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

where Bar extends Foo.

Now, say I had defined foos as

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

My current understanding is that this expresses some unknown type that extends Foo. I take this to mean that any objects that are a subclass of Foo can be added to this list; meaning that there is no difference between ArrayList<Foo> and ArrayList<? extends Foo>.

To test this, I tried to write the following code

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

but was prompted with the following compilation error

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)

Based on my current understanding, I can see why I might not be able to add a Foo to a list of <? extends Foo>, because it is not a subclass of itself; but I am curious as to why I cannot add a Bar to the list.

Where is the hole in my understanding?

Best Answer

As you have discovered by yourself, an ArrayList declared as ArrayList<? extends Foo> subFoos = new ArrayList<>(); would not be very useful.

In order to see the usefulness of <? extends T> consider this:

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

which can later be used as follows:

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

or as follows:

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

note that none of the above would have worked if the collect method had been declared as follows:

List<Foo> collect( List<Foo> a1, List<Foo> a2 )