Polymorphic (Generic) Programming in Java

We have used the fact that Java classes are arranged as a tree with the built in class Object at the root to write ``generic'' or polymorphic code such as the following function to find an element in an array:

  public int find (Object[] objarr, Object o){
    int i;
    for (i = 0; i < objarr.length; i++){
        if (objarr[i] == o) {return i};
    }
    return (-1);
  }

What if we wanted to write a similar program to copy the contents of one array into another? A first attempt could be the following:

  public void arraycopy (Object[] src, Object[] tgt){
    int i,limit;
    limit = Math.min(src.length,tgt.length); // No overflows!
    for (i = 0; i < limit; i++){
        tgt[i] = src[i];
    }
  }

However, notice that it is not enough for the two arguments to be compatible with Object for this function to work correctly: we also need the type of the array tgt to be a supertype of the array src for the assignment to work. In other words, assuming that we have a class Ticket with ETicket as a subclass, the following call is legal

  Ticket[] ticketarr = new Ticket[10];
  ETicket[] elecarr = new ETicket[10];
  ...
  arraycopy(elecarr,ticketarr);

because it is legal to assign ETicket objects to variables of type Ticket. However, the converse call

  arraycopy(ticketarr,elecarr);

would be flagged as a type error at runtime. In general, it is desirable to catch all type errors at compile time, but this kind of error cannot be detected because we have no mechanism to express dependencies across types in polymorphic code.

Let us look at another situation where our existing mechanism for typing is inadequate. Suppose we want to define a linked list that can hold values of any type.

We could begin with a private class to store a node of the list described as follows:

  private class Node {
    public Object data;
    public Node next;
    ...
  }

We can then define our list as follows:

  public class LinkedList{

    private int size;
    private Node first;

    public LinkedList(){
      first = null;
    }
    
    public Object head(){  // return data at head of list if nonempty
      Object returnval = null;

      if (first != null){
        returnval = first.data;
        first = first.next;
      } 

      return returnval;
    }
    
    public void insert(Object newdata){
      ...  // code to insert newdata in list
    }
    
    ...

    private class Node{
      ...
    }

  }

There are two points to note about this style of implementing a polymorphic list:

  1. The implementation loses information about the original type of the element. All data is stored and returned as an Object. This means we have to cast the output of head even when it is clear what the return type is, as shown below:

         LinkedList list = new LinkedList();
         Ticket t1,t2;
    
         t1 = new Ticket();
         list.insert(t1);
         t2 = (Ticket)(list.head());  // head() returns an Object
    

  2. This implementation does not capture our intuitive understanding that the underlying type of the list is uniform. There is nothing to stop us from constructing a heterogeneous list as follows:

         LinkedList list = new LinkedList();
         Ticket t = new Ticket();
         Date d = new Date();
         list.insert(t);
         list.insert(d);
         ...
    

    Once again, the bottleneck is our inability to specify dependency across types within the list: we would like to say that nodes can hold any type of data but the types across all nodes in a list are the same.

One way of addressing these deficiencies is to introduce explicit type variables. This has been incorporated in Java relatively recently (version 1.5) under the name ``generics''.



Madhavan Mukund 2006-02-19