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
Generics are neither covariant nor contravariant. They are invariant
.
Wildcards can be used to facilitate usage of parameterized types. About 95% of what you need to know about them has been summed up by Mr. Bloch in Effective Java (must read) with the PECS rule (Producer Extends Consumer Super).
Assume
interface Owner<T> {
T get();
void set(T t);
}
and the usual Dog extends Animal
example
Owner<? extends Animal> o1;
Animal a = o1.get(); //legal
Dog d = (Dog) o1.get(); //unsafe but legal
o1.set(new Dog()); //illegal
Conversely:
Owner<? super Animal> o1;
o1.set(new Dog()); //legal
Animal a = o1.get(); //illegal
To answer more directly List<? super Dog>
is a list that consumes (adds in this case) Dog
instances (meaning it will consume a Poodle
instance as well).
More generally, the methods of a Foo<? super Bar>
instance that are defined to accept an argument of Foo
's type parameter, can be called with any object referenced compile time with Bar or a sub-type of Bar.