Generic Java
Generic Java
Generics are the Java name for classes or methods that take type parameters. These are a bit like
parameters to a method, except they only carry type information, not values the way variables do.
The motivation for this is that we would like our classes and methods to be type-safe (be able to
detect when the wrong sort of information is passed to them), but often the functionality of the
method or class does not depend on the actual details of the type passed to them.
Taking the example that we have already seen, the ArrayList<E> class doesn't need to know
anything about the structure of the data it stores, but we do need to be able to check that it all has the
same type. So at run-time we need to be able to give E a value, and use it to check things, but we
don't need to know what it is to write the code to add something to the list.
This parameterisation by types is called type polymorphism and is one expression of the principle of
abstraction - we're abstracting away the internal details of the type, and thus allowing our generic
class or method to not have to be rede�ned every time we want to make it work with a new type.
A Motivating Example
Say we want to make a Box to store things in. A Box doesn't need to know anything about what's
stored in it, but Java is a strongly statically typed language, so we need to assign the contents a type.
public StringBox() {
data = null;
}
If we only ever want to Box Strings , then we're happy. If we need to Box something else, we have
to go and copy all this code to another class, and if we want a Box for every possible class we might
use, we're probably going to have a bad time.
The other approach is to make the Box as general as possible. Thanks to Java's class hierarchy, this is
easy (and before generics were added, it was the only sensible way):
public Box() {
this.data = null;
}
This Box can handle anything, but we lose type information, so we have to manually cast things back
to their correct type when we retrieve them from the Box (which means we have to remember or
�gure out the type, and is consequently more error prone than getting the right thing back to begin
with).
A Better Box
The solution to this is to add a type parameter to Box . This will allow Box to handle any type of data,
but will guarantee that the type is what you expect.
private T data;
public Box() {
this.data = null;
}
public T getData() {
return this.data;
}
Now if we create a Box , we are obliged to tell it what the type of the thing it's going to store is, and
the compiler will only allow that type of thing to put into it, and will make sure that's the type of stu�
that comes out of it.
The Syntax of Generics and Type Parameters
Specifying a type parameter is pretty simple. For a class, you just append <[name]> to the class
name, where you replace [name] with whatever you want the type parameter to be called inside that
class (usually beginning with an uppercase letter, because it will be substituted by a class or interface
name when used).
There is also no limit on the number of type parameters, instead of a single name, you simply
provide a comma separated list of names:
If you want a method to have a type parameter (that's not provided by the containing class), the type
parameter list goes before the return type:
For most generic methods, the type can be inferred, so you don't need to specify:
But we won't be making methods with their own type parameters in this subject, so we'll leave that
there.
The major restriction on type parameters is the cannot be instantiated with primitives (as seen in the
ArrayList lesson). So the following is bad:
Each primitive type has a wrapper class associated with it. This list is given in the ArrayList lesson,
so I won't repeat it here.
How Do Generics Work in Java?
This part is beyond what we'll need to know for the subject, so this is just for interest and edi�cation.
Generics are implemented in Java (mostly because they were added to the language relatively late) by
type erasure. This means that all the type checking is done at compile time (which is �ne, as Java is
strongly statically typed - all the type information must be known at compile time for the compilation
to work), so the type safety is guaranteed, but the compiled code just replaces the parameterised
version of the class with the raw, unparameterised version that uses Object wherever there was a
type parameter.
So our Box<T> compiles down to a Box . This means that the type information is not (readily)
available at run time. This causes some problems, such as never being able to create new instances of
a generic parameter type:
A couple of other side e�ects of this approach is that static variables and methods can't have a
generic type, and you can't make generic exception classes.
A positive side e�ect is that Java only needs to compile one version of the code, which is then used at
run time, in contrast to the C++ approach, which recompiles the code for every choice of instantiation
of the type parameters (and plays merry hell with header �le/code �le separations). Functional
languages generally handle this type of polymorphism more naturally.