First, let's define our animal kingdom:
interface Animal {
}
class Dog implements Animal{
Integer dogTag() {
return 0;
}
}
class Doberman extends Dog {
}
Consider two parameterized interfaces:
interface Container<T> {
T get();
}
interface Comparator<T> {
int compare(T a, T b);
}
And implementations of these where T
is Dog
.
class DogContainer implements Container<Dog> {
private Dog dog;
public Dog get() {
dog = new Dog();
return dog;
}
}
class DogComparator implements Comparator<Dog> {
public int compare(Dog a, Dog b) {
return a.dogTag().compareTo(b.dogTag());
}
}
What you are asking is quite reasonable in the context of this Container
interface:
Container<Dog> kennel = new DogContainer();
// Invalid Java because of invariance.
// Container<Animal> zoo = new DogContainer();
// But we can annotate the type argument in the type of zoo to make
// to make it co-variant.
Container<? extends Animal> zoo = new DogContainer();
So why doesn't Java do this automatically? Consider what this would mean for Comparator
.
Comparator<Dog> dogComp = new DogComparator();
// Invalid Java, and nonsensical -- we couldn't use our DogComparator to compare cats!
// Comparator<Animal> animalComp = new DogComparator();
// Invalid Java, because Comparator is invariant in T
// Comparator<Doberman> dobermanComp = new DogComparator();
// So we introduce a contra-variance annotation on the type of dobermanComp.
Comparator<? super Doberman> dobermanComp = new DogComparator();
If Java automatically allowed Container<Dog>
to be assigned to Container<Animal>
, one would also expect that a Comparator<Dog>
could be assigned to a Comparator<Animal>
, which makes no sense -- how could a Comparator<Dog>
compare two Cats?
So what is the difference between Container
and Comparator
? Container produces values of the type T
, whereas Comparator
consumes them. These correspond to covariant and contra-variant usages of of the type parameter.
Sometimes the type parameter is used in both positions, making the interface invariant.
interface Adder<T> {
T plus(T a, T b);
}
Adder<Integer> addInt = new Adder<Integer>() {
public Integer plus(Integer a, Integer b) {
return a + b;
}
};
Adder<? extends Object> aObj = addInt;
// Obscure compile error, because it there Adder is not usable
// unless T is invariant.
//aObj.plus(new Object(), new Object());
For backwards compatibility reasons, Java defaults to invariance. You must explicitly choose the appropriate variance with ? extends X
or ? super X
on the types of the variables, fields, parameters, or method returns.
This is a real hassle -- every time someone uses the a generic type, they must make this decision! Surely the authors of Container
and Comparator
should be able to declare this once and for all.
This is called 'Declaration Site Variance', and is available in Scala.
trait Container[+T] { ... }
trait Comparator[-T] { ... }