I am trying to digest a standard example of using a covariant/contravariant type argument for a Collection and then trying to work out why certain methods behave in a way they do. Here is my example and my (potentially wrong) explanation, and in case of confusion, questions:
List<? extends MyObject> l = new ArrayList<>();
x = l.get(0);
type of x is MyObject
-> this is because compiler realizes that that the upper bound on the type is MyObject
and can safely assume this type.
l.add(new MyObject()); //compile error
The error is caused because, although the upper-bound type of the collection is known, the ACTUAL type is not known (all the compiler knows is that it is the subclass of MyObject
). So, we could have a List<MyObject>
, List<MyObjectSubclass>
or god knows what other subtype of MyObject
as type parameter. As a result, in order to be sure that objects of wrong type are not stored in the Collection, the only valid value is null
, which is a subtype of all types.
Conversly:
List<? super MyObject> l = new ArrayList<>();
x = l.get(0)
type of x is Object, as compiler only knows about the lower-bound. Therefore,. teh only safe assumption is the root of type hierarchy.
l.add(new MyObject()); //works
l.add(new MyObjectSubclass()); // works
l.add(new Object()); //fails
The above final case is where I am having problems and I am not sure whether I get this right. COmpiler can expect any list with a generic type of MyObject all the way to the Object. So, adding MyObjectSubclass
is safe, because it would be OK to add to List<Object>
even. The addition of Object, would however, violate List<MyObject>
. Is that more or less correct? I would be glad to hear more technincal explanation if anyone has it