With List<Animal>
, you know what you have is definitely a list of animals. It's not necessary for all of them to actually be exactly 'Animal's - they could also be derived types. For example, if you have a List of Animals, it makes sense that a couple could be Goats, and some of them Cats, etc - right?
For example this is totally valid:
List<Animal> aL= new List<Animal>();
aL.add(new Goat());
aL.add(new Cat());
Animal a = aL.peek();
a.walk(); // assuming walk is a method within Animal
Of course, the following would not be valid:
aL.peek().meow(); // we can't do this, as it's not guaranteed that aL.peek() will be a Cat
With List<? extends Animal>
, you're making a statement about the type of list you're dealing with.
For example:
List<? extends Animal> L;
This is actually not a declaration of the type of object L can hold. It's a statement about what kinds of lists L can reference.
For example, we could do this:
L = aL; // remember aL is a List of Animals
But now all the compiler knows about L is that it is a List of [either Animal or a subtype of Animal]s
So now the following is not valid:
L.add(new Animal()); // throws a compiletime error
Because for all we know, L could be referencing a list of Goats - to which we cannot add an Animal.
Here's why:
List<Goat> gL = new List<Goat>(); // fine
gL.add(new Goat()); // fine
gL.add(new Animal()); // compiletime error
In the above, we're trying to cast an Animal as a Goat. That doesn't work, because what if after doing that we tried to make that Animal do a 'headbutt', like a goat would? We don't necessarily know that the Animal can do that.
<T extendsAnimal> void foo(List<T> param)
? – Voodoo