|
Articles Index
An Introduction
By Paul Mingardi
November 2002
Generics -- also commonly known as parameterized types -- have been used in other computing languages for years. And now, due to popular demand, generics is slated to be added to the Java language with the 1.5 release.
The Problem with Lists
With generics, types that contain data (such as lists) are not defined to operate over a specific type of data; instead, they operate over a homogeneous set, where the set type is defined at declaration. How exactly does that help the Java developer?
The best way to understand what generics can do for you is to study some Java code that would benefit from generics. The sample below, written to the current Java language specification, contains a list of String object elements and a list of Integer object elements (not the primitive type). Because both elements are subclasses of the Object class, and Java has polymorphic references, you can apply the list's methods to both objects. Unfortunately, as far as the compiler is concerned, the elements in both lists are of the type Object, so the only way to differentiate between the two lists is to use runtime type discrimination on the elements. Even worse, if a developer confuses the two lists and performs an illegal cast on an element, the error is not caught until runtime.
Below are some examples of such errors. In the first example (lines 13 to 20), you might believe you're working with a list of Integer objects, when in reality it's a list of Strings. In the second example (lines 11 and 22 to 27), you might think you're working with a homogenous set of String, but this is a heterogeneous set of both String and Integer elements. So unless you create a new list subclass for every element type (which would undermine the advantages of OO reuse), there's no way to statically constrain the list to a set of homogeneous elements. And in this simple example, the errors are fairly easy to catch. In a bigger program, you'd have even bigger problems.
1..
2. List stringList  = new LinkedList();
3. List integerList = new LinkedList();
4.
5. integerList.add(new Integer(1));
6. integerList.add(new Integer(2));
7.
8. stringList.add(new String("I am a String"));
9.
10. // Nothing constrains the elements to a homogeneous set.
11. stringList.add(new Integer(1));
12.
13. Iterator listIterator = integerList.iterator();
14.
15. // Compiler unaware of the list's return type and the illegal cast.
16. // Developer meant to iterate through the string list.
17. while(listIterator.hasNext()) {
18.
19. // Illegal cast caught at runtime.
20. String item = (String)listIterator.next();
21. }
22.
23. listIterator = stringList.iterator();
24. // No guarantee of homogeneous containers.
25. while (listIterator.hasNext()) {
26. // fail at runtime due to heterogeneous set
27. String item = (String)listIterator.next();
28. }
29..
|
The Generics Advantage
With generics, you achieve polymorphic behavior similar to the example above, but with strong static type-checking; the compiler knows that the two lists are different because they contain different elements, and these lists are guaranteed to contain only a homogeneous set of elements. The sample code below is a translation of the previous example, using generics this time. As you can see from comments in the code, all the errors are caught at compile time. Don't worry about the syntax for now -- we'll cover that shortly.
In comparing the two examples, you should notice that additional type information is included in the generics code, which directs the compiler as to what type each container should contain.
1..
2. import java.util.LinkedList;
3. import java.util.Collections;
4. import java.util.Iterator;
5.
6. public class genericsExample2{
7.
8. static public void main(String[] args) {
9. LinkedList<String> stringList = new LinkedList<String>();
10. LinkedList<Integer> integerList = new LinkedList<Integer>();
11.
12. integerList.add(new Integer(1));
13. integerList.add(new Integer(2));
14.
15. stringList.add(new String("I am a String"));
16. stringList.add(new Integer(1)); // causes a compilation error
17.
18. /* genericsExample2.java:16: cannot resolve symbol
19. ** symbol : method add (java.lang.Integer)
20. */
21.
22. Iterator<Integer> listIterator = integerList.iterator();
23. String item;
24. while(listIterator.hasNext()) {
25. item = listIterator.next(); // causes a compilation error
26.
27. /* genericsExample2.java:25: incompatible types
28. ** found : java.lang.Integer
29. ** required: java.lang.String
30. */
31. }
32.
33. listIterator = stringList.iterator(); // causes a compilation error
34.
35. /* genericsExample2.java:33: incompatible types
36. ** found : java.util.Iterator<java.lang.String>
37. ** required: java.util.Iterator<java.lang.Integer>
38. */
39. // the iterator is guaranteed to be homogeneous
40. while (listIterator.hasNext()) {
41. item = listIterator.next();
42.
43. /* genericsEx2.java:41: incompatible types
44. ** found : java.lang.Integer
45. ** required: java.lang.String
46. */
47. }
48. } // main
49. } // class genericsExample2
|
With these examples, you can understand why generics is among the most-requested additions to the Java language. Here are a few of the benefits:
- The flexibility of dynamic binding, with the advantage of static type-checking. Compiler-detected errors are less expensive to repair than those detected at runtime.
- There is less ambiguity between containers, so code reviews are simpler.
- Using fewer casts makes code cleaner.
Basic Syntax Overview
So far, you've only seen the genericized container classes provided to us. But you can also declare your own generic interfaces, classes, and methods. The following table illustrates the syntax you would use.
| |
Syntax
|
| Paramaterized Type |
Vector<String>
stringVector = new Vector<String>
List<Integer>
integerList = new List<Integer>
|
| Interface |
interface
List<Element> implements MyInterface{...}
|
| Class |
class
MyList<Element> {...}
class
MyList<Element> implements List<Element> {...}
|
| Method |
boolean containsBoth(Element a, Element b);
static <Element> boolean swap(List<Element> list, int i, int j);
|
The following example uses similar syntax in code: MyInterface
Getting Started with Generics
For more information on generics, you can refer to the specification and to the JSR 14. Developers can start writing generics code today using a prototype implementation of the Java compiler, which supports generics (as described in the draft specification). The prototype includes: the source written in the extended language, a jar file containing the class files for running and bootstrapping the compiler, and a jar file containing stubs for the collection classes. It also includes a Java source example and a helpful makefile that compiles the source and invokes the Java Virtual Machine (JVM). Here are the steps to get started using C-shell.
- Download the generics prototype archive file
If you are not already a member of the Java Developer Connection, you will need to register.
Unzip the archive into the installation directory /install_dir
- unzip adding_generics-1_3-ea.zip
- Download and install JDK 1.4.1
- Set the environment variables
setenv JSR14DISTR /install_dir/jsr14_adding_generics-1_3-ea
setenv J2SE14 /jdk_install_dir
- Run the examples
cd /install_dir/jsr14_adding_generics-1_3-ea/examples
Compile and run the Test.java sample using the makefile.
- make
See Also
About the Author
Paul Mingardi is a Market Development Engineer at Sun Microsystems,
working on technology adoption for partners in health care, banking, and finance. He also works with independent software vendors, to help them become successful using Java and other Sun technologies. Paul is a Sun Certified Enterprise Architect and holds a total of seven certifications.
|
|