Bober02
Bober02

Reputation: 15351

Java covarianc/contravariance with add/get

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

Answers (2)

dkateros
dkateros

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

Holger
Holger

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

Related Questions