Co-, Contra- & Invariance in Java
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>
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>
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.
Your code
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
means. But you can't put arbitrary Animals in. And that is why this fails.