For conventional types, we can lift the hierarchy on underlying types to more complex objects. For instance, if S is a subtype of T then S[] is also a subtype of T[]. This is crucial, for instance, to write the generic find function that we began with.
public int find (Object[] objarr, Object o){...}
If we did not have that S[] is compatible with Object[] for every type S, this function would not work in the polymorphic manner that we expect.
However, consider the following situation, where (as usual) ETicket is a subclass of Ticket.
ETicket[] elecarr = new ETicket[10]; Ticket[] ticketarr = elecarr; // OK. ETicket[] is a // subtype of Ticket[] ... ticketarr[5] = new Ticket(); // Not OK. ticketarr[5] // refers to an ETicket!
This generates a type error at runtime. The compiler cannot find fault with the assignment
ticketarr[5] = new Ticket();
because ticketarr is defined to be of type Ticket[]!
With type variables, we can implement polymorphic functions without requiring this troublesome property that the class hierarchy on basic types is extended uniformly to more complex structures built on that type. Thus, for classes defined with type variables, we no longer inherit the hierarchy. For instance, though ETicket extends Ticket, LinkedList<ETicket> does not extend LinkedList<Ticket>.
Suppose we want to write a function printlist that handles arbitrary lists. We might be tempted to write this as follows:
public static void printlist(LinkedList<Object> list){...}
However, since the hierarchy on base types is not inherited by LinkedList, LinkedList<Object> is not the ``most general'' type of LinkedList. Instead, if we want a function that works on all variants of LinkedList, we should introduce a type variable:
public static <T> void printlist(LinkedList<T> list){...}
Madhavan Mukund 2006-02-19