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 get();
}
interface Comparator {
int compare(T a, T b);
}
And implementations of these where T is Dog.
class DogContainer implements Container {
private Dog dog;
public Dog get() {
dog = new Dog();
return dog;
}
}
class DogComparator implements Comparator {
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 kennel = new DogContainer();
// Invalid Java because of invariance.
// Container 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 dogComp = new DogComparator();
// Invalid Java, and nonsensical -- we couldn't use our DogComparator to compare cats!
// Comparator animalComp = new DogComparator();
// Invalid Java, because Comparator is invariant in T
// Comparator 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 to be assigned to Container, one would also expect that a Comparator could be assigned to a Comparator, which makes no sense -- how could a Comparator 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 plus(T a, T b);
}
Adder addInt = new Adder() {
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] { ... }