Skip to main content

Java Generics I - Introduction

Posted by hellofadude on May 22, 2014 at 8:16 PM PDT

In computer programming, generalisation is understood to be a noble pursuit and a fundamental tool for achieving reusability, the idea being that a programmer can be more expressive when he has tools that work with many types. You might recognise the effect of generalisation in many interrelated concepts, for example, like the extension of the inheritance within the single class hierarchy to multiple classes with the use of interfaces that allow a class to be used with multiple types or how polymorphism enables the correct call to be made at runtime when you manipulate objects using base class references.

Java's generics add yet another layer to this pattern of generalisation by providing you with a way to write classes and methods that are not limited to working with just a certain type. The concept is often known as parameterisation and when used in this context refers to the implementation of parameterised types, which are a mechanism that allow you to define unspecified types.

If you can think of an object as being of a type relating to a particular class, an object also acts as a container that can hold other types with get() and set() methods with which to access and manipulate such variables, then you begin to get an idea about the underlying structure of Java objects. Before Java SE 5, you could vary the type a container could hold by upcasting your variables to Object references in a way that allowed you to achieve some generalisation:-

package generics.java;

class State {}
class Country extends State {}

public class ObjectContainer {
     private Object object;
     public ObjectContainer(Object anObject) { this.object = anObject; }
     void set(Object anObject) {    this.object = anObject; }
     Object getObject() { return this.object; }

     @SuppressWarnings("unused")
     public static void main(String[] args) {
         ObjectContainer containerOne = new ObjectContainer(new State());
         ObjectContainer containerTwo = new ObjectContainer(new Integer(10));   // can hold Integer
         State state = (State)containerOne.getObject();                         // must cast Object to correct type
         containerOne.set("Bottles of milk");                                   // can change to hold string
         State stateTwo = (State)containerOne.getObject();                      // Class cast exception!
         String string = (String)object.getObject();                            // cast to String
         Object object3 = object.getObject();                                   // Object no cast necessary
     }
}

By storing your types as Object references, you effectively loosen type restrictions because Object is the root class of all classes. As you can see in the above example, the container accepts Object as argument and returns same type making it possible in the main() for the constructor to accept both State and Integer arguments. However with this technique there will always be the need to make a cast back to the original type when retrieving objects from the container as you can see when calling getObject() method. Using Object as place holders to create more reusable code is useful in certain circumstances but it still has it's own drawbacks. For instance, if you try to cast to the wrong type when retrieving an object, you don't get to know about it until runtime when a class cast exception is thrown. This can be a little bit inconvenient and can also be the source of hard to find bugs.

The ideal solution would be a container that could hold multiple types but which would typically be used to hold one type at any one time and a way to ensure only the correct type can be added to the container. This is what generics allow you to accomplish with the use of a type parameter in the custom <T> to define an unspecified type that can be substituted for an actual type at a later time. The above example can be modified to use generics by simply substituting all occurrence of Object with the type parameter like so:-

package generics.java;
 
public class GenericContainer<T> {
    private T type;
     public GenericContainer(T aType) { this.type = aType;     }
     public void set(T aType) { this.type = aType; }
     public T getType() { return type; }

     public static void main(String[] args) {
         GenericContainer<State> containerOne = new GenericContainer<State>(new State());
         GenericContainer<Integer> containerTwo = new GenericContainer<Integer> (new Integer(20));
         State state = containerOne.getType();    // No Cast Neccesary
         containerOne.set(new Country());         // ok subtype
         //! containerOne.set(new Integer(10));      Error; Method Not Applicable
     }
}

By using the type parameter <T>, we are able to describe types in somewhat more generic terms. You might observe in the main() the GenericContainer class can be used to hold any specified object, as was possible when using the root Object from the previous example, except with generics the syntax requires the replacement of the type parameter with an actual type at runtime. Unlike the previous example, a cast is no longer required when retrieving an element from within the container because the compiler performs type checking to ensure only the correct type or subtype is put into the container.

When you start to think in terms of containers, you begin to observe patterns that may have been previously hidden to you. For one thing Java's collection classes all use generics as well do the Class class, objects of which are used to create objects of your own classes.You will find that containers are a quite common theme in Java and they can be manifested in very many different forms, however the basic idea is always the same. A container contains variables and setter() and getter() methods to access and manipulate those variables.

For example, Java's Stack class represents a last-in-first-out (LIFO) type of container where the last object you put in is the first one you get out. A good example is a magazine clip for an automatic handgun. You will find that such a clip has a sort of spring mechanism that you push down as you load each bullet and pushes up each time you unload one. If we were to try to implement such a mechanism, we might make an abstraction that would allow us to hold bullets in a type of list:-

package generics.java;

import java.util.ArrayList;

class Bullet {
     private static int count = 0;
     private final long id = count++;
     public String toString() {
         return this.getClass().getSimpleName() + " " + id;
     }
}

public class Ammunition<T> extends ArrayList<T> {
     Class<T> type;
     public Ammunition(Class<T> bullet) {
         type = bullet;
     }
     public void put(int size) {
         for(int i = 0; i < size; i++) {
             try {
                 add(type.newInstance());
             } catch (InstantiationException e) {
                 System.out.println(e);
             } catch (IllegalAccessException e) {
                 System.out.println(e);
             }
         }
    }
}

Here, the Ammunition class is a type of ArrayList that can hold any type but in this example will be used to hold Bullet(s). Notice the use of a type tag to enable the retention of type information. A call to the put() method allows us to populate the list with Bullet objects using the Class.newInstance() method.
The following linked Stack is a general purpose container that can be used to hold any type including Bullet like so:-

package generics.java;

public class Stack<T> {
     private static class Element<E>  {
         E type;
         Element<E> currentElement;
         Element() { type = null; currentElement = null; }
         Element(E aType, Element<E> nextElement) {
             this.type = aType;
             this.currentElement = nextElement;
         }
         boolean end() { return type == null && currentElement == null; }
     }
     private Element<T> topElement = new Element<T>();// End sentinel
     public void push(T aType) {
         topElement = new Element<T>(aType, topElement);
     }
     public T pop() {
         T pop = topElement.type;
         if(!topElement.end())
             topElement = topElement.currentElement;
         return pop;
     }
     public static void main(String[] args) {
         Ammunition<Bullet> bullets = new Ammunition<Bullet>(Bullet.class);
         bullets.put(7);
         Stack<Bullet> magazine = new Stack<Bullet>();
         Bullet[] holder = new Bullet[bullets.size];
         int index = 0;
         for(Bullet aBullet : bullets) {
             magazine.push(aBullet);
             holder[index++] = aBullet;
        }
         System.out.print("push(): " + Arrays.asList(holder));
         Bullet aBullet;
         System.out.print("pop(): " + " [");
         while ((aBullet = magazine.pop()) != null)
             if(aBullet.id == 0) {
                 System.out.print(aBullet + "]");
             } else {
                 System.out.print(aBullet + ", ");
             }
     }
}
/* Output
push(): [Bullet 0, Bullet 1, Bullet 2, Bullet 3, Bullet 4, Bullet 5, Bullet 6]
pop():  [Bullet 6, Bullet 5, Bullet 4, Bullet 3, Bullet 2, Bullet 1, Bullet 0]
*//

In this example, the Stack container uses a nested class Element, to hold instances of particular types in this case Bullet objects. Each call to the push() method creates a new Element that holds a link to the previous Element within it. Calling the pop() method retrieves each element in reverse. Notice the topElement object also works like an end sentinel to signal when the stack is empty. The most interesting part of this example is how the nested class is used as a linked container

Generic interfaces

You can also use type parameters with interfaces in much the same way as you do with classes. One of my favorite examples is the Fibonacci sequence. A fibonnaci sequence is a sequence of numbers where each subsequent number is a sum of the previous two. We can use a Generator interface to implement an abstraction of the process of generating a sequence of numbers. In the following example, the next() method will perform this function:-

package generics.java;

interface Generator<T> { T next(); }

public class FibonacciSequence {
     class Sequence implements Generator<Integer> {
     private int seed = 0;
     public Integer next() { return funct(seed++); }
     private int funct(int next) {
         if(next < 2) return 1;
         return funct(next - 2) + funct(next - 1);
     }
}
     public static void main(String[] args) {
         FibonacciSequence outer = new FibonacciSequence();
         FibonacciSequence.Sequence sequence = outer.new Sequence();
         for(int i = 0; i < 20; i++) {
             System.out.print(sequence.next() + " ");
         }
     }
}
/* Output
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765
*//

In this example, we use an inner class to implement the Generator. The algorithm that produces a fibonacci series is implemented in the funct() method which is in turn called by the implemented next() method. The autoboxing mechanism makes it possible for the int primitive returned by the funct() method to be converted to an Integer object expected by the next() method.

To make this class work with the foreach statement, we could use the adapter design pattern to make it iterable like so:-

package generics.java;

import java.util.Iterator;

public class FibonacciAdapter implements Iterable<Integer> {
     FibonacciSequence outer = new FibonacciSequence();
     FibonacciSequence.Sequence sequence = outer.new Sequence()
     int counter;
     FibonacciAdapter(int count) {
         this.counter = count;
     }
     @Override
     public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {
             @Override
             public boolean hasNext() {return counter > 0; }
             @Override
             public void remove() {
                 throw new UnsupportedOperationException();
             }
             @Override
             public Integer next() {
                 counter--;
                 return sequence.next();
             }
         };
     }
     public static void main(String[] args) {
         for( Integer next : new FibonacciAdapter(20))
             System.out.print(" " + next);
     }
}
/* Output
  1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765
*//

When you implement the Iterable<T> interface you must implement the iterator() method to return an Iterator<T> over elements of the specified type. In this example, an anonymous class is used to return an Iterator object that implements methods by which you might iterate over a sequence. Notice here that the type parameter is replaced with the actual type over which we need to iterate. Also notice the use of a counter to control the number of iterations.

Here is a variation of the same idea this time using a nested class, it really is a matter of design preference:-

package generics.java;

import java.util.Iterator;

public class FibonacciAdapterTwo implements Iterable<Integer> {
     int counter;
     FibonacciAdapterTwo(int count) {
         counter = count;
     }
     static class IterableFibonacci implements Iterator<Integer> {
         FibonacciSequence outer = new FibonacciSequence();
         FibonacciSequence.Sequence sequence = outer.new Sequence();
         private int counter;
         public IterableFibonacci(int count) {
             this.counter = count;
         }
         @Override
         public boolean hasNext() { return counter > 0; }
         @Override
         public void remove() {
             throw new UnsupportedOperationException();
         }
         @Override
         public Integer next() {
             counter--;
             return sequence.next();
         }
     };
     public Iterator<Integer> iterator() {
         return new IterableFibonacci(counter);
     }
     public static void main(String[] args) {
         for(Integer next : new FibonacciAdapterTwo(20))
             System.out.print(" " + next);
     }
}
/* Output
  1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765
*//

Note that a nested class by definition has no access to the non-static elements of its enclosing class. To get around this you can create an instance of the nested class in the enclosing class and then pass values through this instance as demonstrated above; or in the alternative, you could choose to make the outer class member variable static if you find this to be convenient.

Generic methods

Generic type parameters can also be used with methods in a way that makes it possible to vary the method independently of it's class and give the effect of the method being somehow overloaded. To parameterise a method, you place the type parameter in angle brackets before the return type like so:-

package generics.java;

class ArbitraryType {}

public class GenericMethod {
     public static <T> String overloaded(T aType) {
         return aType.getClass().getName();
     }
     public static void main(String[] args) {
         System.out.println(overloaded(new ArbitraryType()));
         System.out.println(overloaded(2.55));
         System.out.println(overloaded("A String type"));
         System.out.println(overloaded(2.99F));
     }
}
/* Output
generics.java.ArbitraryType
java.lang.Double
java.lang.String
java.lang.Float
*//

Because of the type parameter and type argument, we are able to pass in any type and hence the effect of the method being overloaded. You could of course specify an actual type in place of the type argument to restrict the use of the method to that particular type, but that is what would happen without the type parameter anyway and of course with generics, you can constrain the type parameter by specifying a bound as a way to exercise more control over the how the method is used.

Here is container that can be used to generate, hold and iterate over any type, in addition to being able to create generic type generator:-

package generics.java;

import java.util.Iterator;

public class TypeGenerator<T> implements Generator<T>, Iterable<T> {
     private Class<T> type;
     private int counter = 0;
     public TypeGenerator(Class<T> aType){
         this.type = aType;
     }
     public TypeGenerator(Class<T> aType, int howMany) {
         this.type = aType;
         this.counter = howMany;
     }
     class IterableGenerator implements Iterator<T> {
         @Override
         public boolean hasNext() {
             return counter > 0;
         }
         @Override
         public T next() {
             counter--;
             return (T)TypeGenerator.this.next();
         }
         @Override
         public void remove() {
             throw new UnsupportedOperationException();

         }
     }
     public Iterator<T> iterator() {
         return new IterableGenerator();

     }
     public T next() {
         try {
             return type.newInstance();
         } catch (Exception anException) {
             throw new RuntimeException(anException);
         }
     }
     public static <T> Generator<T> create(Class<T> aType) {
         return new TypeGenerator<T>(aType);

     }
}

Note the create() method returns a non iterable Generator <T> reference only. Here is how to use the class:-

package generics.java;

public class GenerateTypes {
     public static void main(String[] args) throws ClassNotFoundException {
         Generator pGen = TypeGenerator.create(Piano.class);
         for(int i = 0; i < 9; i++)
             System.out.print(pGen.next() + " ");
         System.out.print("\n");
         TypeGenerator bullGen = new TypeGenerator<>(Bullet.class, 6);
         for(Bullet aBullet : bullGen)
             System.out.print(aBullet + " ");
     }
}
/* Output
Piano Piano Piano Piano Piano Piano Piano Piano Piano
Bullet 0 Bullet 1 Bullet 2 Bullet 3 Bullet 4 Bullet 5
*//

In the GenerateTypes class we use two methods to create TypeGenerator objects. The generic create() method returns a Generator reference and therefore is not iterable, however by using the new keyword to create a new TypeGenerator, a fully iterable object that can be used with the foreach method is returned.

Generics are a very useful addition no doubt, to the Java programming language, and would seem, to the uninformed at least, to be the result of a logical and painless evolution. However it does possess some peculiarities that betray something of it's rather 'interesting' journey with the Java programming language.