Using Java Generics
Create more flexible classes
Have you heard? Generics will be in the
next release of the Java SDK (code
named Tiger, aka JDK 1.5). You might be
wondering "What is a generic?" or "Why
should I care?" or even "Cool! How do I
use them?" This article will introduce
generic coding, explain how generics
are used and what their advantages are,
and discuss how they will impact your
work. To help you understand, I'll
define generics and code a few examples
to illuminate how to use them.
Generics are not a feature that everyone
has used. A concept similar to
generics was included in C++, i.e., templates.
Although the syntax of Java
generics is modeled after C++ templates,
the Java syntax is easier to
understand. In addition, the implementation
of templates and generics are not
the same. Java remains type safe and
doesn't expose source code when supporting
generics. In other words, Java
adds the power of generic coding without
many of the problems found in
other language implementations.
What Are "Generics"?
Generics are known by another
name that makes more sense to the
average coder. They're also called
parameterized types. Using parameterized
types allows you to define a
more flexible typed class or parameter
so you get more type-checking support
from the compiler. They solve one
of the most annoying things in Java
coding - the constant conversion of
object references to a more useful reference
type. This problem is especially
prevalent when using collections of
objects.
To start learning how to use generics,
we'll look at some collectionsbased
examples and see what the advantage is.
Generic Examples
When working with any collection
type, you're working with an object
that can store objects, right? Actually
you're working with an object that can
store java.lang.Objects, but we don't
need to be picky. If you look at Listing
1 you can see that every time you want
to access an object in an ArrayList, you
need to cast that object type. This listing
is a remedial example of the need
to cast all objects returned from an
ArrayList. In this case nothing is done
with the returned string, but it must
be cast in order to access its string
functionality.
In comparison, when using generics
your code would look a little different
(see Listing 2). Instead of creating an
ArrayList reference, create an ArrayList
reference to an ArrayList that can hold
only strings (lines 9 and 10, Listing 2).
The compiler will only allow strings to
be added to this ArrayList, so any
attempt to add objects of any type that
is not a string will generate a compiler
error. In addition, when you retrieve
any value from the ArrayList, it's
returned as a string so no cast is needed
(line 14, Listing 2).
The collections you're familiar with in
JDK 1.4 have been rewritten in 1.5 to
handle generics. The good news is you
won't have to fix or convert your existing
code. Existing code will work with
no changes in most cases. In fact you
can still use the old form of all your
favorite collections, but I'm not sure
why you would, unless you're stuck on pre-1.5 Java.
Why Do We Need Generics?
We don't really need them, but they
add to the language and provide distinct
benefits. Using generics reduces
the need to cast references back to
the actual types of the object, one of
the ugliest little code snippets in all of
Java. As a bonus, the frequency of
ClassCastExceptions should be
reduced because the compiler will
catch type errors, and the need to
surround casts with "if ( o instanceof
)" is also gone. Errors caught by the
compiler are a good thing; runtime
exceptions are not as good. The addition
of parameterized types allows
you to create code that is geared to
collections of objects of a single specific
type.
The following code demonstrates
how a method can expect a collection of
Integers, not just a collection. The
author of this method doesn't need to
check type or cast, and the code is
cleaner because of this. This static
method filters all values larger than a
given cap out of any given collection of Integers.
public static void capList
(Collection list, int cap)
{
Iterator itor = list.iterator();
while (itor.hasNext()){
if(itor.next().intValue() > cap){
itor.remove();
}
}
}
Notice there's no need for the
author of this method to cast Object
to Integer; the compiler will take care
of it. The author of the code defines
what type of collection can be used. If
a user of this method attempts to pass
the wrong type of object, it will be
refused by the compiler, giving a "cannot
be applied" compiler error. The
code below, written to invoke the
"capList()" method, contains the compiler error.
LinkedList list3 = new LinkedList();
list3.add("8");
//The next is a compiler error capList(list3, 30);
Rewriting the same method without
a generic collection would require you
to cast each element retrieved from
the collection. This in itself is not terrible
(heck, we've been doing this in
Java for quite a while), but the generic
example is cleaner. The version that
includes generics is cleaner and the
methods declaration indicates to the
user of the method what is expected as
a parameter. The code itself, rather
than documentation, leaves little
question regarding what the collection
can contain.
For comparison, the same method is
included below without the addition of
generics. Notice the cast in the if-statement
and the use of instanceof to
check the type before using it as an Integer.
public static void capList
(Collection<Integer> list, int cap){
Iterator<Integer> itor = list.iterator();
Object o = null;
while (itor.hasNext()){
o = itor.next();
if ( o instanceof Integer &&
((Integer)o).intValue() > cap){
itor.remove();
}
}
}
Most of the generics changes that
your average Java programmer will see
are in the collections API and in
extending those collections. The collections
become easier to use and the
compiler supports typing, as we saw
above. Gone are the days when you created
your own collection type to hold
only one type of object; generics makes
that code unnecessary.
The way generics change collections
is not the end of the story. Code designers
will be using generics to make code
more flexible in more than just collections.
If the generics change had only
changed collections, Java would have
been better off adding a dynamic array
type.
You'll also be able to work with generics
in your own code. The syntax is new
to non-C++ converts, but it's simple to
work with. As an example we'll create a
class that has a parameterized (generic)
property, a property that can be of any
type. The class in Listing 3 is a standard
JavaBean with a getter and setter, but
the author of this class doesn't need to
know what type is being stored. The
property type is generic, so the author
has inserted a < > after the class name.
In this < > notation is a variable type
name; it needs to be a valid Java identifier
or a comma-separated list of valid
identifiers. After assigning a Java identifier
between the < >'s, you can use that
type name in your class the same as any
other type name. The actual type that it
will be is defined by the user of the code
in Listing 3 .
When the client creates the bean, he
or she will instruct it to hold some
object type. From that point on the
compiler will make checks for type
safety; for example, when one is created
with the following statement:
GenericBean<String> bean = new GenericBean;
After the reference to the GenericBean
is defined as holding strings, the compiler
will allow only strings to be sent into
the setter, and the getX() method will
always return a string. This flexibility and
type safety adds up to easier code to
write and to read.
Generics Issues
Generics represent a fundamental
addition to Java syntax. Changes at
such a low level do not come for free.
Unlike adding new APIs or adding features
to JDK back in the day, this
change will need to be compatible with
millions (billions?) of lines of code.
There are a lot of existing Java applications
and the switch to Tiger might
cause a few errors in your existing Java
code.
In a recent JavaOne technical
talk, Gilad Bracha (lead of JSR 14)
predicted that one out of every
million lines of code will have
compiler errors. This prediction
was based on the two errors found
when compiling the 2+ million
lines of code in the JDK. For my
business 1 in a 1,000,000 seems
pretty trivial, but I don't have
5,000,000 lines of mission-critical
legacy Java to support.
The JCP expert group for generics
worked hard to minimize the impact
the addition of generics can cause.
Because compatibility was a design
constraint, a few annoying features
have been accepted into generics,
including new type casting possibilities.
Specifically, you can still get class
cast exceptions when working with
generics. This is possible because a
reference to a type can be stored in a
reference that doesn't appear to be
parameterized. Whoa, that might
sound a little better in code. The following
example shows an ArrayList
stored in an ArrayList reference. The
new reference to the array list called
list5 allows you to add non-strings to
the ArrayList.
//This snippet will compile fine
ArrayList<String> list4 = new ArrayList<String>();
list4.add("Steve");
ArrayList list5 = list4;
list5.add(new Object());
list4 = list5;
This issue can't be fixed because it's
needed to make the new collections
backward compatible with any code
previously written, but it can cause
trouble. Another way to put it is "It's the
better of two imperfect options." The
compiler allows this change, even
though it will most likely cause a
ClassCastException. This situation will
probably occur rarely, typically when
you're changing existing code that's
used by other parties, but the problem
exists. The compiler will inform you
that you might have an issue by sending
a warning. If you compile with the
-warnunchecked flag, it will provide
details. The above snippet produced
the following warnings.
warning: unchecked call to add(E) as a member
of the raw type java.util.ArrayList
list5.add(new Object());
warning: unchecked assignment:
java.util.ArrayList to java.util.ArrayList
list4 = list5;
Another annoying feature is the limitation
on types. Generic types don't
support primitives. You still can't make a
collection hold int, char, or any of the
primitives. The following code would
fail to compile.
//This doesn't work ArrayList<int> intArray = new ArrayList();
The annoyance here is an old one.
Java separated out primitives from
objects to improve performance and
that's a good thing, but it also forces
developers to learn about "wrapper"
types. If you want to store primitive ints
in a collection, put them into
java.lang.Integer objects.
I mention this annoyance because
I hope that JSR 201 will be included
in JDK 1.5, which would allow you to
ignore the wrapper classes with the
automatic "boxing" of types. If you
want to give it a try, the code for boxing
in Java appears below. Notice
that the ArrayList type allows you to
add ints without making Integers.
The Integer is created behind the
scenes. Boxing is a feature of C# and
VB.NET that makes me just a little
jealous, but they don't have generics yet.
LinkedList list2 = new LinkedList();
list2.add(8);
list2.add(43);
list2.add(4);
Trying It Out
If you want to try out generics, you
don't have to wait for Tiger. A reference
implementation is available now
that works with JDK 1.4.1. This implementation
isn't difficult to use or set
up. To get it going you'll need a little
knowledge and two downloads. You
need JDK 1.4.1, not JDK 1.4. You can
find it at http://java.sun.com; download
and follow the installation directions.
You'll also need an implementation
of the generics compiler and the
new definitions for the collections.
These are contained in a zip file called
adding_ generics-2_0-ea. zip, which
can be downloaded from Sun at
http://developer.java.sun.com/developer/earlyAccess/addinggenerics/index.html.
Finally, you'll need to register as a
member of the Sun Developer
Network. Joining is painless. After
downloading the zip, unzip it to a folder.
I downloaded it onto my Windows
box and unzipped it to c:\java. The zip
contains a folder named "adding_
generics-2_0-ea" that I'll call
JSR14HOME when describing the commands
needed to compile and run
your generics examples. The generics
download contains JSR 14 features
(Generics) as well as JSR 201 features
(enumeration, boxing, static imports,
and the "for-each" enhancement).
Sample code is included as well as the
source code for many of the changes to
existing collections. There is a .bat file
to help compile in the scripts directory,
but I wrote my own because it was
untested and required adding environmental
variables I would never use
again.
When compiling you need to include
a few extra arguments for javac. The following
is the javac statement I used to
compile. JSR14HOME refers to the location
of your adding_generics-2_0-ea
folder and the JDK141Home is the
Java_Home directory for an install of
the JDK version 1.4.1.
javac -J-Xbootclasspath/p:JSR14HOME\gjcrt.jar-bootclasspath JSR14HOME
\collect.jar;JDK141HOME\jre\lib\rt.jar -
source 1.5 -d classes - classpath classes
FileName.java
The Java command contains similarly
obscure arguments. The command I
used in running the generic examples
is:
java -Xbootclasspath/p:JSR14HOME\gjc-rt.jar - cp classes FileName
If you're developing on a Unix platform,
a make file provided in the JSR 14
download should help you on your way.
You can find it in the examples directory in JSR14HOME.
Conclusion
Changes to the core syntax of Java are
rare because of the momentum behind
Java. You can't change anything without the
risk of affecting millions of lines of code.
The generics addition to Java is a welcome
one, even with that risk. After years of
debating if generics was a good idea and
evolving generics compilers, Sun will be
sending generics out with Tiger in JDK 1.5.
This is a great change because these
"generics" or "parameterized types" allow
you to create more flexible classes that are
still types supported by the compiler.
Generics also solve one of the
biggest annoyances in Java code: the
constant type-checking with instanceof
followed by type-casting. The compiler
will catch many of the errors that
would have generated ClassCastExceptions at runtime. This will make
code more readable and type safer.
The addition of generics is a significant
change. Although Java has gone
through many versions, none has added
as much syntax change as generics,
except maybe inner classes. Maybe it's
time to move the major version up one
and announce JDK 2.0?
References
"Preparing for Generics":
http://developer.java.sun.com/developer/
technicalArticles/releases/generics/
JSR 14 Home: http://jcp.org/en/jsr/detail?id=14
Early access release:
http://developer.java.sun.com/developer/
earlyAccess/adding_generics/index.html
"JDK 1.5 Preview: Addition of Generics
Improves Type Checking": www.devx.com/Java/Article/16024
Author Bio
Steve Close has been working as a Java consultant and trainer since 1997. He has authored seven popular workshops for Intertech on topics ranging from Complete Java Programming to
Expert J2EE Patterns. He served as president of the Twin Cities Java Users Group from 1997 to 2000.
sclose@intertech-inc.com