The upper bound wildcard is necessary for a Collection of type T. Let's take the following structure
class Animals{}
class Dog extends Animals{}
class Cat extends Animals{}
class Container<T>{
void add(T t){};
void addAll(Collection<T> t){};
}
And we instantiate it and use it like this
ArrayList<Dog> dogs = new ArrayList<>();
dogs.add(new Dog());
ArrayList<Cat> cats = new ArrayList<>();
cats.add(new Cat());
Container<Animals> animalContainer = new Container<>();
animalContainer.add(dogs.get(0)); // will work since add(T t)
animalContainer.addAll(cats); // will not work since addAll(Collection<T> t)
The reason why it will not work with addAll(Collection<T> t)
is that the container will only accept the type T
which is Animal
, but since the cat list is of type Cat
it violates the constraints.
Since Animal
can be a Dog
, Cat
, AnyOtherConcreteAnimal
. By changing it to an upper bound wildcard
void addAll(Collection<? extends T> t){};
You say that you expect an Collection with the concrete type of Animals
and not only T
which is Container<Animals>
. And now this will work again
animalContainer.addAll(cats); // will work since addAll(Collection<? extends T> t)