I was reading an interesting dzone article on covariance in java which is pretty easy to follow but there is one thing bugging me which doesn't make sense, the article is here https://dzone.com/articles/covariance-and-contravariance
I am quoting examples from the article here where it is explaining why a collection cannot be added to:
With covariance we can read items from a structure, but we cannot write anything into it. All these are valid covariant declarations.
List<? extends Number> myNums = new ArrayList<Integer>();
Because we can be sure that whatever the actual list contains, it can be upcasted to a Number (after all anything that extends Number is a Number, right?) However, we are not allowed to put anything into a covariant structure.
myNums.add(45); //compiler error
This would not be allowed because the compiler cannot determine what is the actual type of the object in the generic structure. It can be anything that extends Number (like Integer, Double, Long), but the compiler cannot be sure what
The paragraph above is what doesn't make sense to me, the compiler knows that the list contains Number
or anything that extends it, and the ArrayList
is typed to Integer. And the compiler knows about the literal int that is inserted.
So why does it enforce this as it seems to me like it can determine the type?
You are missing two points:
You are thinking in terms of local variables only:
public void myMethod() {
List<? extends Number> list = new ArrayList<Integer>();
list.add(25);
}
A compiler could easily detect the actual value of ?
, here but I know of nobody who would write such a code; if you are going to use an integer list you just declare the variable as List<Integer>
.
Covariance and contravariance are most useful when dealing with parameters and/or results; this example is more realistic:
public List<Integer> convert(List<? extends Number> source) {
List<Integer> target = new ArrayList<>();
for (Number number : source) {
target.add(number.intValue());
}
return target;
}
How is a compiler expected to know which is the type used to parametrize the list? Even if at compile time all the calls only pass instances of ArrayList<Integer>
, at a later time code can use the method with a diferent parameter without the class being recompiled.
The paragraph above is what doesn't make sense to me, the compiler knows that the list contains Number or anything that extends it, and the ArrayList is typed to Integer. And the compiler knows about the literal int that is inserted.
No, what the compiler knows is that the list contains something that extends Number
(including Number
). It cannot tell if it is a List<Number>
(in which case you may insert any instance of Number
) or a List<Integer>
(in which case you may only insert Integer
instances). But it does know that everything you retrieve using get
will be an instance of Number
(even if it is not sure about the specific class).