The introduction of generics into the Java programming language will alleviate the need for the explicit cast and the potential for a runtime ClassCastException in Listing 1. The intent is that type inconsistencies can be caught for the most part (see "The Hard Parts: Pitfalls and Complexities of Generics") at compile-time rather than runtime.
So let's jump in and get our feet wet with some example code using genericity. For the first example, see Listing 2, in which we rewrite the code in Listing 1 using the new generics features.
Listing 2 Collections example with genericity
1 protected void collectionsExample() { 2 ArrayList<String> list = new ArrayList<String>(); 3 list.add(new String("test string")); 4 // list.add(new Integer(9)); this no longer compiles 5 inspectCollection(list); 6 } 7 8 9 protected void inspectCollection(Collection<String> aCollection) { 10 Iterator<String> i = aCollection.iterator(); 11 while(i.hasNext()) { 12 String element = i.next(); 13 } 14 }
The first thing to notice is the new syntax we used on line 2 to create an instance of ArrayList. In Java 1.5, the ArrayList (and most other classes in the Collections API) will become a generic or parameterized type. A parameterized type is a class that defines a set of types (T1, T2...Tn) that must be provided for each valid declaration. Listing 3 shows part of the new class definition for ArrayList.
Listing 3 Partial java.util.ArrayList source
1 public class ArrayList<E> extends AbstractList<E> { 2 // details omitted... 3 public void add(E element) { 4 // details omitted 5 } 6 public Iterator<E> iterator() { 7 // details omitted 8 } 9 }
The E in "class ArrayList<E>" is defined as a type variable. This type variable is an unqualified identifier; it does not explicitly specify a type; it serves as a placeholder for a type to be defined when the ArrayList is used.
On line 2 of Listing 2, we declare an instance of an ArrayList and bind the type String to the type variable E with the syntax "new ArrayList<String>()". As such, when the method public boolean add(E o) {...} is called on this instance, E is logically bound to the String type as if the method had been declared as follows:
public void add(String element) { ... }
Because the type variable "T" is used throughout the new implementation of ArrayList, all methods that previously would have taken an Object as a parameter or returned an Object are now constrained by the parameter type. You can see the results of this in our example code. In line 4 of Listing 2, where we try to add an Integer to our ArrayList<String>, we get a compile-time error. When extracting elements from our collection in line 12, we are able to retrieve elements without the type cast required in our first example, Listing 1. In effect, our instance of ArrayList<String> behaves as if it were coded to contain only instances of String.
A major goal in adding generics to Java is to "increase type safety without type variation." Otherwise stated, the intent is to enable more compile-time type checking through more declarative interfaces without requiring the developer to code different classes for different scenarios, such as an "ArrayList of String" versus an "ArrayList of Integer." From this notion, we can see that an "ArrayList<String>" is essentially a new type, and is treated by the compiler as such.
Based on this simple example, we can see the following benefits of genericity:
We now have Collections that are type safe (restricted to contain elements of a single type) without type variation. For example, in line 4 of Listing 2, any attempt to add an element of a type other than String to this collection will cause a compile-time error.
Type casts are now implicit rather than explicit. With a homogeneous collection of Strings, retrieval of any element within the Collection no longer requires an explicit cast (see line 12 of Listing 2).
The Java language is now more declarative and provides stronger interface contracts. Line 9 of Listing 2 makes it very clear to the client (caller) of the inspectCollection(...) method that a "Collection of Strings" is the required parameter type.
Type mismatch errors are caught at compile-time rather than at runtime.
Constraining Type Variables
Though many generic classes are designed to work with any class (or, more correctly, Object and its descendants), type parameters can be constrained using the following syntax:
public class C1<T extends Number> { } public class C2<T extends Person & Comparable> { }
In the first example, the type passed as a parameter to C1 must be Number or a subclass of Number. In the second example, the type parameter must extend Person and implement Comparable.