Generic Classes in Java

Consider the following (incomplete) Java class, instances of which represent stacks containing values of type int.

            public class StackOfInt  {
               ...
               public int topOf() { ... }
               public void push(int item) { ... }
               ...
            } 

If we wanted to form a stack of float values, or of String objects, or of Person objects (assuming that we had a class Person), the above class would be of no use. To acquire the ability to form stacks containing float values, we could make a new class (called, say, StackOfFloat) that would be identical to the class above except that every relevant occurrence of "int" would be changed to "float". Similarly, we could make a new class, StackOfPerson, by changing every relevant occurrence of "int" to "Person".

But this approach is ridiculous, because it leads to a proliferation of nearly identical special-purpose classes. This is undesirable for several reasons, among them being that it is more difficult to manage large collections of software components than to manage small ones. For example, suppose that an error is discovered in one of these StackOfX classes, or that a way to improve it is found. Then the modifications made to it (in order to correct or to improve it) should be repeated in all the other nearly-identical classes!

What we would really like is to have a single, general-purpose Stack class that can be used to manipulate stacks containing any kind of data items. Different programming languages offer different ways of devising software components having this kind of generality. In Ada, there are so-called generic packages and in C++ there are templates. Essentially, what Ada's generic packages and C++'s templates provide are a means of constructing software components having type parameters, which are analogous to the (data) parameters found in subprograms. Such a component is instantiated by a client program that supplies the desired data type. For example, a program that employed stacks containing items of type Person would instantiate the generic Stack package (in Ada) or Stack template (in C++) and, in so doing, indicate that Person is the data type of the items that are to be placed onto the stack(s).

Prior to the release of Java 5.0 (i.e., J2SE 1.5) in summer of 2004, which included Java Generics, Java did not have this capability. That is, there was no way to design a single Stack class, for example, such that we could create one instance, say stkOfInt, allowing only Integer objects to be pushed onto it, and another instance, say stkOfPeople, allowing only People objects to be pushed onto it. The best we could do was to design a Stack class such that instances of it allowed any object (i.e., any instance, direct or indirect, of Java's "granddaddy" class Object) to be pushed onto it.

Such a general-purpose class could look like this:

            public class Stack  {
               ...
               public Object topOf() { ... }
               public void push(Object item) { ... }
               ...
            }  

Notice that each time we refer to the data type of an item on the stack (or that is to be pushed onto it), we use "Object" instead of "int" (or "Person", or whatever). Because Object is the name of the class of which all others are descendants (and hence any object created is an instance, at least indirectly, of Object), this means that instances of any class may be placed onto the stack. (Unfortunately, this excludes values of primitive types, such as int, float, boolean, and char. In order to place such a value onto a stack using this class, we would have to "wrap" the value in an instance of a so-called wrapper class (e.g., Integer, Float, Boolean).)

Here is an excerpt from a client program:

     import Stack;
     import Person;
     import KeyPadLock;

     public class ... {

        public void doSomething(...) {
           Stack s = new Stack();
           s.push( new Person(...) );
           s.push( new KeyPadLock(...) );
           s.push( new Integer(37) );
           ...
        }
        ...
        ...
     }  

The program constructs a stack, s, and then pushes onto it (references to) newly-constructed objects of type Person, KeyPadLock, and Integer, the last of which wraps the int value 37.

This example illustrates that objects of any kind (i.e., values of any reference type) —and, notably, of different types— may be placed onto a stack created using this class. That is, our Stack class provides the capability of creating and manipulating what could be termed heterogeneous stacks.

Heterogeneity is often useful and desirable. However, sometimes our intent is to create and manipulate stacks that are necessarily homogeneous, i.e., in which all items are instances (direct or indirect) of some class that is a proper descendant of Object (e.g., Person, Integer). Prior to the advent of Java Generics, there was no mechanism in Java by which such a homogeneity restriction could be enforced "automatically". For example, there was no way to make the push() method in a general-purpose stack class throw an exception in response to an attempt to push a KeyPadLock object onto a stack intended to hold objects of type Person. (Nor was there a way to do something even better, which would be to express the stack class in such a way that the compiler could recognize that a client was trying to push the "wrong" kind of object onto a stack, and thereby produce a syntax error message.)

One irritating consequence of our stack class being heterogeneous is the need for its clients to do type casting when observing the value at the top of the stack. (Type casting is a way to "tell Java" that a particular expression should be interpreted as being of a particular type.) For example, if a client is using an instance s of class Stack containing KeyPadLock objects, in order to observe the one at the top of s it would probably use the expression

(KeyPadLock) s.topOf()

rather than the simpler
s.topOf()

This is because, for example, the declaration/assignment

KeyPadLock k = s.topOf()

is syntactically illegal, as the right hand side is of type Object and the left hand side is of type KeyPadLock. (To be legal, an assignment x = E must be such that the declared data type of x is an "ancestor" of E's type. Instead, we would write the above as

KeyPadLock k = (KeyPadLock) s.topOf()

so that the both sides are of type KeyPadLock. Lest we leave the impression that Java allows any object to be interpreted as being of any type we wish, we should mention that an exception will be thrown (what kind??) if an object is cast to a type to which it does not conform.

In the case of a heterogeneous stack containing objects of "unrelated" types (and where the type of the object at the top of the stack cannot be predicted), it is likely that the client would have a code segment resembling the following:

         Object obj = s.topOf()
         if (obj instanceof KeyPadLock) {
            KeyPadLock k = (KeyPadLock) obj;
            < code to process k (which is just obj viewed as a KeyPadLock object) >
         }
         else if (obj instanceof Person)
            Person p = (Person) obj;
            < code to process p >
         }
         else if ...
The instanceof operator determines whether a given object (its first operand) is an instance of a given class (its second operand).

The more likely scenario is one in which a stack contains objects of related types (e.g., from subclasses of Person such as, say, Student, Employee, etc.) and in which the client code would make use of dynamic dispatch in processing the objects as they are popped.