15-200 Lecture 18 (Thursday, June 19, 2008)

Generics

Sometimes we write code that is heavily algorithmic. We describe manipulations that can be applied to many different data structures. Sorting is an example of this type of algorithmic code. Given a function that can compare the objects, the same manipulations can be used to sort objects of any type.

When we write this type of code in Java, we often make use of Interfaces. In the case of sorting, we make use of, for example, the Comparable interface. But, this isn't a complete solution. It requires that the class implement some common interface. It doesn't allow for us, for example, to pass in function-objects that manipulate it. Of course, we can pull this off by using things such as Comparators. But, we hit a roadblock when it comes to primitives.

It would be nice if we had a way to describe the algorithm and then let the type get plugged in after-the-fact. And, that is exactly what Java's generics mechanism does for us. It allows us to implement an algorithm in Java, but use a placeholder paramter, almost like a configuration constant, for the type. Then, when we actually go to use the generic class, we just plug in the type and off we go.

Generics aren't new. C++ has "templates". Java's "generics" look a lot like templates. The big difference is that templates are instantiated before compiling, whereas Java's generics are actually processed by the compiler. In C++, templates are almost entirely "cookie cutter code" -- mechanical substitutions. In Java, because of the type system, life is a bit more complicated. Java needs to insert some bridge code to do runtime type checking.

Generic Collections

Java's Collections package has been rewritten using generics. This means that it is no longer necessary to cast from Object when accessing items within a collection -- it is possible to create a Collection of the proper type.

Let's take a quick look. The following code is basically Java 1.4 code. It uses the Collection class, but doesn't provide any of the new type information, so it looks exactly the same

  import java.util.*;

  class GenericsExample {

    public static void main(String[] args) {

      List l = new LinkedList();

      l.add ("Hello");
      l.add ("Great");
      l.add("World");

      System.out.println (l);
      System.out.println ((String) l.get(0));

    }
  }
  

And, it will compile and run under 1.5. But, it'll produce a warning:

  Note: GenericsExample.java uses unchecked or unsafe operations.
  Note: Recompile with -Xlint:unchecked for details.
  

This warning is basically telling us that it is defaulting to Object so it can't do any type checking for us.

Take a look at the version below. Notice the type information provided within the <aligator-brackets>. This provides a type to use in place of the default Object. Specifically, this example makes the list a list of Strings instead of a list of Objects. This is really nice -- notice that the ugly-but-usual cast from Object to String is missing on the get().

  import java.util.*;

  class GenericsExample {

    public static void main(String[] args) {

      List<String> l = new LinkedList<String>();

      l.add ("Hello");
      l.add ("Great");
      l.add("World");

      System.out.println (l);
      System.out.println (l.get(0));
    }
  }