Friday, July 1, 2011

Subtyping of Generic Types - Parametrized Type Invariance

If a reference of an Apple instance can be assigned to a reference of a Fruit, as seen above, then what's the relation between, let's say, a List<Apple> and a List<Fruit>? Which one is a subtype of which? More generally, if a type A is a subtype of a type B, how does C<A> and C<B> relate themselves?

Surprisingly, the answer is: in no way. In more formal words, the subtyping relation between generic types is invariant.
List<Apple> apples = new ArrayList<>();
List<Apple> foulApples = apples; //fine 
Collection<Apple> greenApples = apples; //fine again

However this is not fine:
List<Apple> apples = new ArrayList<>();
List<Fruit> fruits = apples;//Error

and so does the following:

List<Apple> apples;
List<Fruit> fruits = ...;
apples = fruits;

But why? Is an apple is a fruit, a box of apples (a list) is also a box of fruits.

In some sense, it is, but types (classes) encapsulate state and operations. What would happen if a box of apples was a box of fruits?

List<Apple> apples = ...;
List<Fruit> fruits = apples;
fruits.add(new Strawberry());

If it was, we could add other different subtypes of Fruit into the list and this must be forbidden.

The other way round is more intuitive: a box of fruits is not a box of apples, since it may be a box (List) of other kinds (subtypes) of fruits (Fruit), such as Strawberry.

So what if they were covariant?

Please go through the comments to see what is the problem:
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(122));
List<Object> lo = li; // Problem starts here
Object num = lo.get(0); // Nothing wrong with this
lo.add("Bang!"); // This would be OK through
// lo but what about li
Integer i = li.get(1); // ClassCastException

No comments:

Post a Comment