In Java, you can use the extends keyword to declare that a given type parameter is covariant. Covariance and contravariance do confuse me a bit, but I think I've gotten a general sense of them. However, in my tests, it seems that Java type parameters are inherently covariant. So why can we explicitly state it?
For example, I built a quick sample that looks like this:
public class Main<E> {
protected E value;
public Main(E value) {
this.value = value;
}
public <T extends E> void setValue(T value) {
this.value = value;
}
public E getValue() {
return value;
}
public static void main(String[] args) {
Main<Number> example = new Main<Number>(0L);
System.out.println(example.getValue().getClass().getCanonicalName());
example.setValue(32.0);
System.out.println(example.getValue().getClass().getCanonicalName());
}
}
The type used is Number, the superclass of all the boxed types. In the constructor, it takes a parameter which is declared of type E (in this case Number). In the setValue method, on the other hand, it takes a parameter which is declared to be anything extending type E. Both println calls return the proper value (first java.lang.Long, then java.lang.Double).
The way I see it, removing generics, you can still pass subclasses. If the class was declared with no type parameters and the methods/constructor took/returned Numbers, it'd still work properly.
So what is the purpose of the extends keyword in this case?
In this case, there is no benefit to using extends in setValue. You're right that the T declared by setValue could be replaced by its upper bound, E:
public void setValue(E value) {
this.value = value;
}
But consider this instead:
public <T extends E> T setValueAndGiveItBack(T value) {
this.value = value;
return value;
}
That means we can do this:
Double d = example.setValueAndGiveItBack(32.0);
Without T, the most specific type example.setValueAndGiveItBack could return would be Number.
Just to clarify, you're also right that type parameters are inherently covariant. The reason for using extends is to restrict a type parameter's upper bound. For example, <T> is implicitly <T extends Object>. In the above example, without declaring E as T's upper bound, we wouldn't be able to assign value to this.value since it could be any Object.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With