Tuesday, 3 May 2011

Are Generics Overly Complicated?

Generics, introduced in Java 5, are widely used in Java programs today especially in the arena of collection classes. What are not so widely used and little understood by average programmers are generics that use bounded wildcards (and don’t forget that by definition nearly everyone is average).

Take two classes: Fruit, and Apple which extends Fruit>. The following will work, because Apple extends Fruit.

    List<Fruit> fruitList = new ArrayList<Fruit>();
(new Fruit()); // Okay - the same type
fruitList.add(new Apple()); // Okay - Apple subclass of fruit

In the above example, we specified that we have a list of Fruit objects, which includes sub-classes of Fruit such as Apples. So now take the logic one step further and declare a list that can contain anything that extends Fruit using bounded wildcards.

The first attempt below fails because the compiler hasn’t got a concrete type which it can instantiate:

    // Will fail - doesn't know how to instantiate this list
List<? extends Fruit> moreFruit = new ArrayList<? extends Fruit>();

Therefore, if you give the compiler a concrete type to instantiate, you do get a valid list... sort of:

    List<? extends Fruit> apples = new ArrayList<Apple>()// Okay - Apples extends fruit

...but if you try adding objects to this list you’ll come unstuck:

    apples.add(new Fruit());    // Error - incompatible types of '? extends Fruit' and 'Fruit'
apples.add(new Apple());    // Error - incompatible types of '? extends Fruit' and 'Apple'

… which means that you have a list to which nothing can be added - except null:

    apples.add(null);        // Okay

The reason for this seems to be that in declaring List<? extends Fruit> you are saying that you want a list that can hold anything that’s a sub-class of Fruit, including, for example, Oranges. The concrete implementation is Apples and you don’t know whether or not you have Apples or Oranges until run-time. This defeats one of the main points of Generics: using compile time checking, rather than run-time checking and so the compiler disallows this sort of code.

No comments: