enerics is one of the hottest features of Sun Microsystems’s upcoming JDK 1.5 (Tiger) release. Programmers and researchers have been clamoring for a feature like this for years, and an impatient few have even implemented their own prototypes already. Generics enables the creation of parameterized classes and methods, allowing Java developers to create custom variations of their code for different types.
Unlike JDK 1.4, Tiger isn’t just a set of new standard libraries?it features actual changes to the language itself. Generics is the most profound of these changes. Long a part of advanced academic languages, this new feature is a welcome addition to Java.
This article introduces the new generics feature by examining Sun’s example pre-release implementation. It demonstrates how to use generics in a sample program, an extensible networked multimedia framework (NMF). Finally, it goes over using generics in the Collections classes.
What Is Generics?
Generics is very similar to C++ templates, both in syntax and semantics. However, like most things in Java that are similar to C++, it is simpler. Since generics has a clear syntax, the following quick example will go a long way in explaining it:
public class Mailbox { private Thing thing; // ...}
This is a fragment from the source code for NMF. The first thing you should notice is the “
” following the class name. This is a type parameter, which says you can create variations of the Mailbox
class by providing different types to take the place of Thing
. This means that Mailbox
is a parametric class, a class that takes one or more parameters.
Thing
isn’t really a type or class?you haven’t created a file called Thing.java
that contains a class definition for Thing
. Rather, you’ll have to supply something to take the place of Thing
. Here’s how you instantiate the parametric class:
Mailbox myMailbox = new Mailbox();
Now Mailbox
clearly is the full name for a Java type. It means “the Mailbox
class, with Thing
replaced by String
.” You could also use Mailbox
, which would be “the Mailbox
class, with Thing
replaced by URL
.”
In NMF, a Mailbox
is an object that lets you send and receive messages of different types. A Mailbox
, for example, lets you send and receive URL
s, while a Mailbox
lets you send and receive String
s.
The nice thing about this is that you don’t have to create two different classes to send and receive two different kinds of messages. Instead, you can create a single class, parameterized by the type variable Thing
. You simply program this class to send and receive Thing
s. Instantiating the Mailbox
class with various types replaces the Thing
type variable with actual types such as String
and URL
.
What Value Does Generics Add?
You may think Java doesn’t really need a mechanism of this type. After all, without generics, you could just write Mailbox
so that it sends and receives objects of type Object
, and add in a little bit of casting to get the objects to by the type you need. But generics provides two useful features.
First and foremost, generics offers better compile-time type checking. In a sense, a cast is just a run-time type check. Since it happens at run time, it also could fail at run-time. With generics, the type casting is implicit in the instantiation you are using? Mailbox
vs. Mailbox
vs. Mailbox
?and it’s done at compile-time. By using a particular instantiation, you are in essence saying this Object
is really going to be a String
, and the compiler will verify whether this is consistent with everything else going on in the program.
Secondly, generics provides convenience. Casting can be irritating?you can feel like you’re telling the compiler something that should be obvious. It also makes code harder to read, since it can turn a simple assignment (or parameter-pass) into a more complicated expression. With generics, casting just goes away. This might sound like a small thing, but try generics for a while and then go back to casting?you’ll miss generics.
The NMF Example
NMF is, as previously mentioned, a simple, extensible networked multimedia framework. Put another way, it’s a glorified chat with graphics. The NMF client contains two sub-clients: a chat window and a shared whiteboard window. The chat window is what you would expect: type something in the text field and this sub-client sends the message to all users. The shared whiteboard is like the chat, but for drawing: draw some lines and this sub-client sends the drawing to all users. Figure 1 shows these windows in the NMF client.
![]() |
||
Figure 1. The NMF Client: This NMF client has a chat window (on the left) and a shared whiteboard (on the right). |
The nice thing about NMF is that it is extensible. If you want to create more sub-clients, you can. The application Figure 1 shows is only one way of setting things up. You can easily create an interface with more clients, arranged any way you want.
To run the program, you’ll need to run the server like so:
% java Server 5000
The 5000
is the port number on which you want the server to listen. You can change this value, but you’ll have to change the port number in sample.html
as well. Sample.html
is the client-side applet file. You can run the client by visiting this HTML page in your browser, or by running appletviewer like this:
% appletviewer sample.html
Either way, you should see a window like the one in Figure 1.
Now that you have the program running, the following sections describe how it works and how it uses generics.
The Client Class
The Client
object establishes communication with the server. The sample application, which has a chat and a whiteboard, creates a single Client
object to share between the chat and the whiteboard. If you had more sub-clients, then they would share the Client
object as well.
The construction for Client
takes a hostname and port:
Client client = new Client( hostname, port );
Once you have an instance of Client
, you can pass it to each of the sub-clients.
The Mailbox Class
The central class in this program is Mailbox
, which provides a convenient way to exchange objects with the server. Each sub-client should have its own Mailbox
. But Mailbox
is a parameterized class?you need to specify what kind of object each Mailbox
is supposed to send and receive. For example, the chat sub-client sends and receives strings, so it creates its Mailbox
like this:
Mailbox mailbox = new Mailbox( client, "chat" );
The first parameter to the constructor for Mailbox
is the Client
object, which lets the Mailbox
talk to the server. The second parameter defines the channel that this Mailbox
will use. Each sub-client uses a different channel. That way, you make sure that chat messages go only to other chat sub-clients and not to, say, audio sub-clients.
The Chat Mailbox
Using Mailbox
is quite simple. Here’s how you send a text string:
mailbox.send( "hello chat server" );
Because this Mailbox
is parameterized on the String
type, its send()
method takes a String
as an argument. Likewise for the receive()
method:
String message = mailbox.receive();
Take a look at the Chat.java file included in the source code download. You can see how simply the chat sub-client is constructed using the Mailbox
class.
The Whiteboard Mailbox
The whiteboard is a bit more complicated. It lets you draw lines into a window and these lines show up on everyone else’s screens. So you need to send lines across the network.To handle this, you create a class called Line
, which contains the (x ,y) positions of a line’s endpoints. To send and receive such objects, you create an appropriate Mailbox
:
Mailbox mailbox = new Mailbox( client, "whiteboard" );
Here, you’ve used the Line
class instead of the String
class. It doesn’t matter that Line
is a class that you created and that String
is built into Java?any class will do.
Generics Code Meets Non-generics Code
Internally, Mailbox
doesn’t do all that much. When an object is sent, the Mailbox
puts the object on the Client
‘s send queue, and when an object comes in to the Client
, the Client
puts it on the Mailbox
‘s receive queue.
The interaction between the two is interesting, however. Mailbox
is a parameterized type, which means that each separate instance deals with a different message type. Client
, on the other hand, deals only with Object
types. In this sense, Client
is written in the old, pre-generics style. Code written using generics inevitably will have to interact with code not written with generics. Such instances will require a cast.
In the case of NMF, the cast happens when objects are coming from the server. The Client
determines to which Mailbox
the object should go. It treats the Mailbox
as an Enqueuer
, which is an interface defined like this:
public interface Enqueuer{ public void enqueue( Object object );}
The Client
passes the Object
that it has received to the Mailbox
via this method. Here’s Mailbox
‘s implementation of enqueue()
:
public void enqueue( Object object ) { Thing thing = (Thing)object; readQueue.put( thing );}
Here’s where you’ve hidden the inevitable cast. Client
deals with Objects
, while Mailbox
deals with Thing
s, and here is where the two meet.
In actuality, an interface isn’t required here?it’s just a nice way of structuring things. But the important thing is to recognize that generics-based code and non-generics-based code have something of an impedance?the generics code does away with run-time type checks, while the old-style code still needs them. When objects are passed from old-style code to new-style code, it generally means a typecast will be produced?and thus the possibility of a type exception.
Generics and Collections Classes
The NMF program actually uses generics in several other places, but not by defining new parameterized classes. Rather, it uses existing parameterized classes from the Java Collections classes.
The Collections classes in the java.util
package are used to create various kinds of object collections (lists, sets, maps, and so on). They also provide useful iteration classes. However, they can be a bit annoying. During your Java programming, you’ve probably typed something this more times than you’d like to remember:
for (Enumeration e = vector.elements(); e.hasMoreElements();) { String s = (String)e.nextElement();}Or, using the newer classes, something like this:for (Iterator it = list.iterator(); it.hasNext();) { String s = (String)it.next();}
It’s a lot to type over and over. Generics can get rid of that annoying cast. All of the Collections classes have been turned into parameterized classes, generally in the obvious way. For example, a List
of Strings
can be created like this:
List myStrings = new ArrayList();
And you can use it like this:
for (Iterator it = list.iterator(); it.hasNext();) { String s = it.next();}
See? The cast is gone. Of course, you had to add that
in there, so you haven’t actually saved any typing in this case. But you would if you used the variable it
more than once inside the loop.
The real win will come when the full JDK 1.5 feature set is implemented. It will contain a new for
syntax that will make iteration very concise:
for (String s : list) { // ...}
It seems that Java’s designers have finally realized the centrality of iteration, and have made it as concise as possible. As noted language hacker Paul Graham has said, “succinctness is power.” And that for loop is about as concise as you can get.
Using Collections Classes
This section looks at a few places where the NMF program uses the Collections classes.
As you’ll recall, the Client
receives messages from the server and hands them to the appropriate Mailbox
, based on the channel on which the message was sent. Client
carries out this mapping using a java.util.Map
object. More precisely, a Map
that maps channels (Strings
) onto Mailbox
es, where the Mailbox
is represented as an Enqueuer
:
private Map mailboxes = new HashMap();
Using the Map
is now cast-free:
Enqueuer mailbox = mailboxes.get( channel );
Parameterized collections are used in a few other places in the code:
- The
WhiteBoard
class uses aSet
to store the set of lines that has been drawn. - The
Server
class uses aList
to store objects that respond toClient
requests. - The
Queue
class (not part of collections) is a simple blocking queue, parameterized by the type of the object it contains. This class is used as the read queue inMailbox
and the write queue inClient
.
Finally, Generics in the JDK
Generics is a well-understood language formation that has not caught on particularly well in commercial languages, but it has a strong theoretical underpinning and a solid prototype implementation that is available now.
Generics does more than simply eliminate extra casts. It provides extra typing information to the compiler, allowing it to perform certain typing checks at compile-time that previously had to be done at run-time. In combination with the Collections classes and the new for loop syntax, generics can be very powerful. Watch for it in the upcoming release of JDK 1.5?finally.