Reputation: 15351
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
Upvotes: 1
Views: 129
Reputation: 1594
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.
Upvotes: 3
Reputation: 298389
Don’t get fooled by thinking about the behavior of objects you have just created. There’s no sense in giving them wildcards. Wildcards are there to give freedom to method parameters. Look at the following example:
public static <T> void sort(List<? extends T> list, Comparator<? super T> c)
This means there must be a type T
for which the list guarantees that all elements are of type T
(might be a subclass) and that the provided Comparator
implementation can handle elements of T
(might be more abstract).
So it is permitted to pass in a List<Integer>
together with a Comparator<Number>
.
Upvotes: 1