3

I’m learning generics in Java, and I am learning Covariance and contravariance. I understood covariance and why can’t we write in to covariance type. But for me contravariance is confusing. I want to know why reading from contravariance is always Object type

Lets say we have the following classes

Class Animal
Class Dog extends Animal
Class Tiger extends Animal

Now, consider the following code

List<Animal> animals = new ArrayList<>();
List<? super Animal> contraVarianceList=animals;

animals.add(new Dog()); // Allowed
animals.add(new Tiger()); //Allowed
Animal animal = contraVarianceList.get(0); // Not allowed. Why?

My question is why cant we read “Animal” from the contravariance list. Why is it always and only “Object” that can be returned? What will or may go wrong if we read “Animal” from the contravariance list?

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Sundaravel
  • 464
  • 5
  • 16
  • [I'm not sure this is a duplicate, but related](https://stackoverflow.com/questions/2723397/what-is-pecs-producer-extends-consumer-super) – Benjamin Urquhart Jun 15 '19 at 16:21

1 Answers1

5

I presume you meant to ask why is Animal animal=contraVarianceList.get(0); not allowed.

Think about it this way:

List<Animal> animals=  new ArrayList<>();
List<Object> objects = new ArrayList<>();

List<? super Animal> contraVarianceList = animals;
List<? super Animal> contraVarianceList2 = objects;

animals.add(new Dog()); // Allowed
animals.add(new Tiger()); //Allowed

objects.add(new Dog()); // Allowed
objects.add(new Tiger()); //Allowed

//there is no type difference between contraVarianceList and contraVarianceList2
Animal animal = contraVarianceList.get(0);  //compiler will complain
Animal animal2 = contraVarianceList2.get(0); //compiler will complain

The compiler cannot really know that any of them is carrying only Animal instances. You explicitly said ? super Animal so it could also be List that is carrying plain Object.

Usually you use these types of lists for parameters of methods where you are expecting to add items rather than read from the list. So having something like this:

void populateAnimals(List<? super Animal> animals) {
  //whatever code
  animals.add(...);
}

Guarantees that you can use the method with both List<Object> and List<Animal>.

jbx
  • 21,365
  • 18
  • 90
  • 144
  • Now I understand. Thank you so much for your neat and clear explanation – Sundaravel Jun 15 '19 at 16:57
  • I have been going over this for a week and I can't understand why Contravariance exists, Im struggling with this –  Mar 05 '23 at 09:37
  • @theMyth Let's say you have a deeper class hierarchy `Object -> Mammal -> Dog` and `Object -> Mammal -> Tiger`. If you have a method `addDog(List animals)` that does `animals.add(new Dog())` you cannot pass a `List` to it. On the other hand if you change the signature to `addDog(List super Dog> animals)` you could. It gives the caller of the method more flexibility as to which collection types can be used. – jbx Mar 05 '23 at 10:26
  • Thanks @jbx, I got that, I am struggling to understand why this is needed though and is there a practical use case, because i can't think of one. Maybe I don't need to be concerned, but I want to have a deeper understanding. Why would I need to pass an object of Mamal to an addDog method, I can't understand –  Mar 05 '23 at 13:26
  • It is not about the object being passed to the list. We are still adding an object of type `Dog` to the list. We are talking about the type of the list itself. Using `List super Dog>` allows you to use `List`, `List` or `List` as the argument. Using `List` only allows you to use `List`. Thats the only difference. – jbx Mar 05 '23 at 23:25