An Introduction to Generics in Java
Although Java's 1.4 release focused on enhancements to the core libraries (logging, regular expressions, "new" IO), the upcoming 1.5 release contains a number of changes to the language itself. One of the most anticipated and most significant changes is the addition of "genericity."
What Is Genericity?
Genericity (or "parameterized types") is a mechanism for clients to specify the types of objects that a class can work with via parameters passed at declaration-time and evaluated at compile-time. As a result, starting with Java 1.5, collections, such as java.util.ArrayList, can be bound to contain specific types of objects. Java's implementation of generics will provide for more compile-time type safety, which will enable the development of stronger and more self-describing APIs.
Generics for Java was formally proposed through the Java Specification Request 014 in June 2003. The Java Standard Development Kit 1.5 is expected to be released in the summer of 2004.
Before we get too deeply into the world of genericity, let's begin by looking at the current Collections framework in the Java 2 SDK. The root interface of all collections classes is Collection. Inspection of the Collection interface reveals that all concrete collections are collections of Objects at runtime. Although this provides a desirable level of flexibility and abstraction, it weakens the supplier's contract (public interface) and thereby places additional burden on the client (the calling class).
For example, review the code in Listing 1.
Listing 1 Collections example without genericity
1 protected void collectionsExample() { 2 ArrayList list = new ArrayList(); 3 list.add(new String("test string")); 4 list.add(new Integer(9)); // purposely placed here to create a runtime ClassCastException 5 inspectCollection(list); 6 } 7 8 9 protected void inspectCollection(Collection aCollection) { 10 Iterator i = aCollection.iterator(); 11 while (i.hasNext()) { 12 String element = (String) i.next(); 13 } 14 }
Listing 1 contains two trivial methods. The first [collectionsExample()] simply creates a Collection of type ArrayList and adds an Integer and a String to the Collection. The second [inspectCollection()] iterates through the elements, typecasting each to a String.
So what's the problem, you ask? Because the Collection classes internally treat elements in the collection as Objects, retrieval of the elements requires an explicit cast (see line 12 of Listing 1). This forces the developer to "know" what the actual types of the elements are, either through the API or by reflection. Because the downward cast cannot be checked by the compiler, there is always a risk that a runtime ClassCastException could be thrown. For example, the above code will compile, but line 12 will generate a ClassCastException when it attempts to cast the second element to a String (because it is really an Integer.) These issues add to the complexity of our code while still leaving our code prone to fatal runtime errors.