Reading

Required: These notes!
Recommended: Java in a Nutshell, Chapter 4, section "Generic Types".

Overview

Java Generics provide us with a mechanism for writing "generic" code, in the sense that one piece of code can work with many different types.

Java Generics

Interfaces give us a way to write code that works very generally, meaning that it can be used for many types. Essentially, we write code (like our sorting method from last class) where we say "as long as the type you have implmenents the interface we require, you can use our code on it". However, there's a serious limitation to the use of inferaces or regular old base classes (which amounts to the same thing) to provide "generic" code like this. In fact, the limitation came up (though I blithely ignored it in class) even wth our simple examples. The limitation is this: type information gets lost.

This problem is most easily seen with the help of a simple concrete example.

public class MyPair
{
  Object x, y;
  public MyPair(Object o1, Object o2)
  {
    x = o1;
    y = o2;
  }
  public Object first() { return x; }
  public Object second() { return y; }
}
Now let's imagine we're using this:
MyPair p = new MyPair("ying","yang");
What is the type of p.first()? Well it is an Object. That means the only methods that can be called with it are the few common to every Object. Nothing else is OK. In our example, we know the result is actually a String, but
p.first().length()
won't compile. Our only alternative is to cast, like this
String s = (String)p.first();
int n = s.length();
Not the biggest deal in this small example, but it gets painful in bigger examples. More importantly, each of these casts could fail, because we may unwittingly put the wrong type of object into the MyPair. Nothing checks these types for us as we compile, so if we make such a mistake, it's as a runtime error, which is bad. (Imagine if this code was for a heart monitor?)

Java provides us with another way to handle these problems: generics. Generics allow you to write code where types become parameters, i.e. a variable name refers to a type — like int, String or Double[ ] — rather than referring to a value of a fixed type. Type parameters are indicated by <name>, where name is the name of your type parameter. For example, below we rewrite MyPair using a typeparameter named T, and after the declaration, we literally search-and-replace "Object" with "T":

public class MyPair<T>
{
  T x, y;
  public MyPair(T o1, T o2)
  {
    x = o1;
    y = o2;
  }
  public T first() { return x; }
  public T second() { return y; }
}
To instantiate a MyPair that stores Strings, we give String as a type argument for the type parameter T.
MyPair<String> p = new MyPair<String>("ying","yang");
It's important to note that the type of p is MyPair<String>, i.e. the type argument is a part of the full typename. Thus, the type of p is different from MyPair<Scanner>, for example. Moreover, MyPair by itself is not a type, since no type for the typeparameter T is specified.

The big payoff in this is that the compiler enforces that only Strings may be added to the pair p and, therefore, it knows that the type of p.first() is String. Thus,

p.first().length()
compiles no problem.

You'll never need to implement another Queue ...

Generics are especially valuable for "containers", i.e. classes that store and retrieve objects for you, but which don't much care what the objects are: like MyPair above. Our running favorite example of a container is ... Queue. Presumably, it's bothered you along the way that we have to keep recreating our Queue class everytime there's something different we want to store. With generics, we don't have to do that. Literally, all we have to do is, starting with our Queue of Strings, modify our delcaration like this
public class Queue<T> implements Iterable<T>
and systematically search for String and replace it with T. If we do that, we get a Queue class that works for any type.

Now, if we want a Queue of Scanners it would be Queue<Scanner> Q = new Queue<Scanner>();. If we want a Queue of Strings it would be Queue<String> Q = new Queue<String>();. Here's a simple program that shows this fact off.

~/$ java Ex2
I am 23 years and 5 months old on 3 March 2015
I am years and months old on March 23 5 3 2015

And now ArrayList ... the answer to all your prayers

With the help of generics, we can turn to the biggest cheater's feature of the Java API: the ArrayList! (ArrayList API documentation) ArrayList is a generic container that gives us array-like convenience for accessing elements (the method .get(i) gives us access by index) with linked-list like convenience for adding new elements (the method .add(x) adds a new elment to the end of an ArrayList. There are even methods for inserting or removing elements at arbitrary positions. In fact, I fear you will be less enthralled with our good friend Queue now that you've met ArrayList.

~/$ java Ex3
I am 23 years and 5 months old on 3 March 2015
I am years and months old on March 23 5 3 2015