This is part of the concept of contravariance applied to the generic concept of Java.
Generic Covariance
Just to contextualize, covariance occurs when we use extends
and we allow a more specific type (subclass) to be used instead of a more generic type.
Let's look at the example of a covariant method:
java.util.ArrayList#addAll(java.util.Collection<? extends E>)
Now suppose the following class hierarchy:
class Animal { }
class Gato extends Animal { }
class Cachorro extends Animal { }
If we have a list of Animal
, we can add lists of any subtype:
List<Gato> gatos = new ArrayList<>();
List<Cachorro> cachorros = new ArrayList<>();
List<Animal> animais = new ArrayList<>();
animais.addAll(gatos);
animais.addAll(cachorros);
Covariance is by far the easiest to understand and most used.
Generic contravariance
Occurs when we use super
and allow a more generic type (superclass) to be used instead of a more specific type. It's practically the opposite of covariance.
Let's look at the example of contravariance quoted in the question:
java.util.ArrayList#forEach(Consumer<? super E> action)
Now suppose the following class hierarchy:
class Animal {
void darBanho() { }
}
class Gato extends Animal { }
class Cachorro extends Animal { }
The idea here is to be able to get a list of Cachorro
or a list of Gato
both to receive Consumer<Animal>
.
Therefore, the goal of contravariance applied to generics is to enable the reuse of generic code.
Example:
List<Gato> gatos = new ArrayList<>();
List<Cachorro> cachorros = new ArrayList<>();
gatos.forEach(Animal::darBanho);
cachorros.forEach(Animal::darBanho);
Another example:
List<Animal> animais = new ArrayList<>();
List<Gato> gatos = new ArrayList<>();
List<Cachorro> cachorros = new ArrayList<>();
Collections.copy(animais, gatos);
Collections.copy(animais, cachorros);
In the example above, the copy
method has the following signature:
void copy(List<? super T> dest, List<? extends T> src)
In other words: the target list ( dest
) can be of any generic type that is a superclass of the generic type of origin ( src
). In this case, Animal
is superclass of Gato
and Cachorro
.
So the use of super
in the method reinforces that the destination list can always receive elements from the source list since you can always assign a specific type to a more generic type.
Contravariance is also somewhat counterintuitive, but it is easier to understand if you think it is often interesting to treat an object or collection of objects as its more generic type for some kind of generic processing.