HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML

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. [email protected]

    "Using Java Generics"
    Vol. 8, Issue 11, p. 36

    	
    
    
    Listing 1
    
    1 package example1;
    2
    3 import java.util.ArrayList;
    4
    5 public class OldCollection{
    6
    7 public
    8 static void main(String[] a){
    9 ArrayList strLst =
    10 new ArrayList();
    11 strLst.add("hello");
    12 strLst.add("goodbye");
    13 //...
    14 //Object reference
    15 String strFromList =
    16 (String)strLst.get(1);
    17 }
    18 }
    
    Listing 2
    
    1 package example1;
    2
    3 import java.util.ArrayList;
    4
    5 public class UseGenerics{
    6
    7 public
    8 static void main(String[] a){
    9 ArrayList<String> strLst =
    10 new ArrayList<String>();
    11 strLst.add("hello");
    12 strLst.add("goodbye");
    13 //...
    14 String strFromList =
    15 strLst.get(1);
    16 }
    17 }
    
    Listing 3
    
    1 class GenericBean<VarType>{
    2
    3
    private VarType x;
    4
    5
    public GenericBean(){}
    6 public GenericBean(VarType x){
    7 this.x = x;
    8 }
    9
    10 public VarType getX(){
    11 return x;
    12 }
    13 public void setX(VarType x){
    14 this.x = x;
    15 }
    16 }
    

    All Rights Reserved
    Copyright ©  2004 SYS-CON Media, Inc.
      E-mail: [email protected]

    Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.