27
JANUARY, 2005
Java generics
By John Hunt
The
Java 2 Platform Standard Edition 5.0 Development Kit (JDK 5.0) was released
during September 2004. It extended the Java language in a number of ways.
One particular way was to add the concept of Generics to the Java language.
At this point, you may well ask "what are generics?". They are
essentially data structures that can be configured for a particular data
type at run time. To see why generics are useful consider the following
piece of Java code:
package com.planetjava.collection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Illustrates pre generics loop processing
*
**/
public class LoopExample {
public static void main(String [] args) {
List list = new ArrayList();
list.add("John");
list.add("Denise");
Iterator it = list.iterator();
while (it.hasNext()) {
String st = (String)it.next();
System.out.println(st);
}
}
}
This program
adds two strings to an array list collection object. We then use an iterator
to loop through the contents of the list. Each element in the list is
retrieved (via the it.next()
statement). However, to store it in a string it must be cast back to a
string. This is because, although the object referenced by the ArrayList
list, is a string, the only thing that can be guaranteed by Java at the
point of retrieval form the list is that it is an object. This works but
makes for more complex code as well as offering the possibility that the
list may contain other types of object. We could of course protect against
this by adding an instanceof
test to ensure the object is a string, for example:
import java.util.*;
public class LoopExample {
public static void main(String [] args) {
List list = new ArrayList();
list.add("John");
list.add("Denise");
Iterator it = list.iterator();
while (it.hasNext()) {
Object obj = it.next();
if(obj instanceof String) {
String st = (String)obj;
System.out.println(st);
}
}
}
}
However,
with generics, it is possible to mark a collection as only be allowed
to hold a particular type of data. We will look at this in the next section.
Using
Generics with collections
Generics allow a collection to be restricted to a particular data type
in Java. That is, to a particular class or interface type. This allows
code to be less cluttered as well as allowing the compiler to perform
additional compile time type checking.
If we consider
the program presented earlier and look at what its generic based version
would be:
import java.util.*;
public class GenericsExample {
public static void main(String [] args) {
List<String> list = new ArrayList<String>();
list.add(new String("John"));
list.add(new String("Denise"));
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String st = it.next();
System.out.println(st);
}
}
}
Both versions
of the program will print out two strings to the standard output (e.g.
command window or console). The only difference is in the programs themselves.
In the Generics example, we have marked the List
as only being allowed to contain Strings. This is now a list of strings
– and what’s more, the complier and runtime environments will
ensure that this is the case. This when we retrieve a string form the
list we do not need to cast it to a String
– Java already knows it will be a string.
At first
sight, you may think that all we have done is to move some of the syntax
around. After all, we have still had to specify that the List will hold
only strings and that the Iterator
will iterate over string objects. Surely, this just means we have sugared
the language with a slightly different way of casting our objects.
Although
at first glance this appears to be what has happened, the implications
are far deeper (as was hinted at above). The Java environment now knows
that this is not just a List
but a List of Strings
and will check at compile time to ensure only Strings are being added
to the list and at run time to ensure that only Strings are actually added
to the list. This should of course improve the reliability of Java programs
in general (and may also improve the readability as you will know the
type of object intended for a list when the list is declared – rather
then when the object is retrieved at some point later on!).
It is worth
sticking with the use of generics with collections a little longer to
examine one more feature of generics. What is created when you make an
instance of a collection using generics. For example, if we write:
List<Integer>
intList = new ArrayList<Integer>();
List<String> stringList = new ArrayList<String>();
What type
of object is referenced by the variables intList
and stringList?
Are they instances of a class ArrayList<Integer> and ArrayList<String>,
instances of some other unspecified class or instances of ArrayList?
One way to
test this is to execute the following two statements:
System.out.println(intList.getClass().getName());
System.out.println(stringList.getClass().getName());
The result
of executing these two statements is:
java.util.ArrayList
java.util.ArrayList
Which may
surprise you. The reason that both intList
and stringList are
instances of the class ArrayList
is that they are both ArrayList
objects but they have been parameterised to only hold certain types of
object. That is they are both ArrayLists
but one has been told to only hold Integer objects and the other to only
hold String objects.
Generics
in declarations
Generics are very useful with collections of objects and you will find
that the new Iterator
interface and the new collection classes in the Collections
class hierarchy have all been updated to allow them to be parameterised
for a specific type of object. Of course, it is not only when using collections
that generics may be useful. What if you are creating your own data type
and what to allow programmers to parameterise that as well? You can do
exactly this using some new syntax added to Java.
Let us suppose
we wish to create a very simple Queue
class (the J2SE version 5.0 has added some Queue
support finally but we will ignore that for the moment). This queue class
will define three methods, add,
isEmpty and pop.
This is a very simple queue class as I do not want to confuse the issue
with other data structures or inheritance. Instead, this queue is extremely
limited as it can only hold a single item of data at a time:
import java.util.*;
public class Queue<E> {
private E data;
public void add (E x) {
data = x;
}
public boolean isEmpty() {
return data == null;
}
public E pop() {
return data;
}
}
If we examine,
the definition of this new class we can see a number of things. Firstly,
the name of the class appears to be Queue<E>.
Actually, the "E"
in the angle brackets is the declaration of the formal type parameter
for our Queue (note
that I have selected E as that is what is used within the collections
classes in Java, however, any letter could be used, for example, I could
have chosen X).
The next
thing to note is that the type of the instance variable data is "E"
( the formal type parameter from the class name). Thus, the type of "data"
will be determined at compile time based on how the Queue
class is parameterised. If we say later on:
Queue<String>
q = new Queue<String>();
Then the
type of "data" will be String.
In a similar manner the parameter of the method add
is also E. And thus,
the actual type of the parameter will be determined when Queue
is used. This is also true of the method pop,
which has a return type of E.
In many ways,
what we are doing is partially defining our Queue.
We are leaving out the exact type of data to be held, passed in and returned
until the Queue
class is actually used in the code.
So, we can
now write a main method test harness that uses the new Queue
class thus:
public static void
main(String [] args) {
Queue<String> q = new Queue<String>();
q.add("John");
System.out.println(q.pop());
}
Generics
in Java
A quick word of caution about the concept of generics - from the above
description it may appear that what we are doing is creating a template
for a class that is in some ways made into a concrete class when we use
it. That is that Queue<E>
is a template for a QueueString,
QueueInteger or
QueuePerson class.
Whilst this may be a useful analogy to draw, it is not technically accurate.
This is because Java does not actually create different versions of the
Queue class. Thus, just as with ArrayList,
there is only one Queue
class in the system, but in this case they have been instantiated to hold
Strings, Integer
or Person objects. |