Consider the following code:
public class Main {
static class Animal {}
static class Dog extends Animal {}
static List<? extends Animal> foo() {
List<Dog> dogs = new ArrayList<>();
return dogs;
}
public static void main(String[] args) {
List<Animal> dogs = Main.foo(); // compile error
}
}
I'm trying to understand why it won't compile. Meaning, why doesn't the compiler let me refer to List<? extends Animal>
List<? extends Animal>
as a List<Animal>
? Is that has something to do with the type erasure mechanism?
A List<Animal>
is a List
to which you can add any Animal
(or null), and everything you take out of it will be an Animal
.
A List<? extends Animal>
List<? extends Animal>
is a list which contains only a specific subclass of Animal
(or null), and you don't know which one; this allows you to treat everything you take out of it as an Animal
, but you aren't allowed to add anything to it (except for literal null
).
A List<? extends Animal>
List<? extends Animal>
can't act as a List<Animal>
, because that would allow you to do this:
List<Cat> listOfCats = new ArrayList<>();
List<? extends Animal> listOfSomeAnimals = listOfCats; // Fine.
List<Animal> listOfAnimals = listOfSomeAnimals; // Error, pretend it works.
listOfAnimals.add(new Dog());
Now, because listOfCats
, listOfSomeAnimals
and listOfAnimals
are all the same list, the Dog
has been added to listOfCats
. As such:
Cat cat = listOfCats.get(0); // ClassCastException.
Because List<? extends Animal>
List<? extends Animal>
would allow any subclass of Animal. List would simply allow objects of the Animal class.
In List<? extends Animal>
List<? extends Animal>
are also objects like cat or dog allowed. If you initiate this with a "pure" dog-list, you can't tell from the outside that it's not allowed and therefore it doesn't compile.
This is about Co-, Contra- and Invariance. Covariance tells us about what we can take out, Contravariance tells us about what we can put in, and Invariance tells us about both.
Invariance
List<Animal>
is invariant . You can add any Animal, and you are guaranteed to get any Animal out - get(int)
gives us an Animal
, and add(Animal)
must accept any Animal. We can put an Animal in, we get an Animal out.
List<Animal> animals = new ArrayList<Dog>()
is a compiler error , since it doesn't accept Animal
or Cat
. get(int)
still gives us only Animals (Dogs are Animals, after all), but not accepting the others is a deal-breaker.
List<Animal> animals = new ArrayList<Object>()
is likewise a deal-breaker. Yes, it accepts any animal (we can put Animals in), but it gives us Objects.
Contravariance
List<? super Dog>
List<? super Dog>
is contravariant . We can only put Dogs in, put nothing is said about what we get out. Thus, we get Object out.
List<? super Dog> dogs = new ArrayList<Animal>();
this works, because we can put a Dog into it. And Animals are Objects, so we can get objects out.
List<? super Dog> dogs = new ArrayList<Animal>();
// dogs.add(new Animal()); // compile error, need to put Dog in
dogs.add(new Dog());
Object obj = dogs.get(0);
// Dog dog = dogs.get(0); // compile error, can only take Object out
Covariance
List<? extends Animal>
List<? extends Animal>
is covariant . You are guaranteed to get an Animal out .
List<? extends Animal> animals = new ArrayList<Cat>();
works, because cats are Animals, and get(n)
gives you Animals. Granted, they are all Cats, but Cats are Animals, so this works out fine.
Adding stuff is harder, though, since you don't actually have a type that you can put in:
List<? extends Animal> animals = new ArrayList<Cat>();
//animals.add(new Cat()); // compile error
//animals.add(new Animal()); // compile error
Animal animal = animals.get(0);
List<? extends Cat> cats = new ArrayList<Animal>();
is a compiler error , because you can take out any animal - but you require that the only thing that can be taken out is Cats.
static List<? extends Animal> foo() {
List<Dog> dogs = new ArrayList<>();
return dogs;
}
Here, everything is fine. foo()
is a List where you can take out Animals. You surely Since Dogs are Animals, and you can take out Dogs, you you can take out Animals. Everything you take out of the List is guaranteed to be an Animal.
List<Animal> dogs = Main.foo(); // compile error
You are saying that dogs
is a List where you can put in any Animal
, and you are guaranteed to get Animals out. The last part is easy, yes, you are guaranteed to get Animals out, that is what ? extends Animal
? extends Animal
means. But you can't put arbitrary Animals in. And that is why this fails.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.