Generics : List<? extends Animal> is same as Li

2020-01-24 02:01发布

问题:

I am just trying to understand the extends keyword in Java Generics.

List<? extends Animal> means we can stuff any object in the List which IS A Animal

then won't the following also mean the same thing:

List<Animal>

Can someone help me know the difference between the above two? To me extends just sound redundant here.

Thanks!

回答1:

List<Dog> is a subtype of List<? extends Animal>, but not a subtype of List<Animal>.

Why is List<Dog> not a subtype of List<Animal>? Consider the following example:

void mySub(List<Animal> myList) {
    myList.add(new Cat());
}

If you were allowed to pass a List<Dog> to this function, you would get a run-time error.


EDIT: Now, if we use List<? extends Animal> instead, the following will happen:

void mySub(List<? extends Animal> myList) {
    myList.add(new Cat());     // compile error here
    Animal a = myList.get(0);  // works fine 
}

You could pass a List<Dog> to this function, but the compiler realizes that adding something to the list could get you into trouble. If you use super instead of extends (allowing you to pass a List<LifeForm>), it's the other way around.

void mySub(List<? super Animal> myList) {
    myList.add(new Cat());     // works fine
    Animal a = myList.get(0);  // compile error here, since the list entry could be a Plant
}

The theory behind this is Co- and Contravariance.



回答2:

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.



回答3:

It is not. List<Animal> says that the value which is assigned to this variable must be of "type" List<Animal>. This however doesn't mean that there must only be Animal objects, there can be subclasses too.

List<Number> l = new ArrayList<Number>();
l.add(4); // autoboxing to Integer
l.add(6.7); // autoboxing to Double

You use the List<? extends Number> construct if you are interest in an list which got Number objects, but the List object itself doesn't need to be of type List<Number> but can any other list of subclasses (like List<Integer>).

This is sometime use for method arguments to say "I want a list of Numbers, but I don't care if it is just List<Number>, it can be a List<Double> too". This avoid some weird down casts if you have a list of some subclasses, but the method expects a list of the baseclass.

public void doSomethingWith(List<Number> l) {
    ...
}

List<Double> d = new ArrayList<Double>();
doSomethingWith(d); // not working

This is not working as you expecting List<Number>, not a List<Double>. But if you wrote List<? extends Number> you can pass List<Double> objects even as they aren't List<Number> objects.

public void doSomethingWith(List<? extends Number> l) {
    ...
}

List<Double> d = new ArrayList<Double>();
doSomethingWith(d); // works

Note: This whole stuff is unrelated to inheritance of the objects in the list itself. You still can add Double and Integer objects in a List<Number> list, with or without ? extends stuff.