Page 2 of 6
Prior to generics, the only way to write a generic data pool was to return objects of type Object. But that return value forces the application to cast the object to the real type, with code like this:
Car c = (Car) dataPool.get();
Such casts have two problems. First, they interfere with readability -- especially if you want to invoke a method on the returned object, because you wind up with a mess like this:
Car myCar = ((Car) dataPool.get()).initialize(make, model);
With generics, that code turns into something more readable:
Car myCar = dataPool.get().initialize(make, model);
But the second -- and larger -- issue with casts is that bugs in programs that use them show up as runtime errors. As Gilad said, a cast amounts to a declaration in which "we hope and pray" the variable contains an object of the indicated type at runtime. If it doesn't, the app is hosed.
On the other hand, with generics, the data pool's contents are documented, both for the human reader and the compiler. Because the compiler knows what the pool is supposed to contain, errors are discovered at compile time instead of runtime. That shift speeds application development and improves confidence in the final product.
The declaration for a generic class, method, or interface uses angle brackets to indicate the generic part of the declaration:
interface List<E> {
...
}The above code defines an interface for a generic list class. Typically, single-character names indicate the generic component, but any valid identifier could be used.
Within the declaration, the angle brackets are no longer needed, so the identifier E would refer to the generic data type within the body of the declaration. For example:
interface List<E> {
void add(E x);
...
}List<Integer> myList = new LinkedList<Integer>();
(Note: In 1.5, the Collections API will be fully genericized, so you'll be able to use the code above. On the other hand, the generics
facility has been cleverly designed so that it is totally backward compatible. As a result, old code of the form List myList = new LinkedList() will continue to run exactly as it did before. That code will still need to cast the data types, but it will run unchanged
against the libraries' generic version.)
The data type named in a usage statement could itself be a generic type too. The declaration for a list of lists, for example, would look like this:
List<List<Integer>> myList;
(Note: Type aliases are currently being considered as an extension to the language in order to simplify such declarations. But the expert group is looking for a way of doing so that avoids adding (abusable) C-style typedefs to the language.)
A generic declaration can also name multiple generic types:
interface Function<A, B> { B value(A arg); }The declaration above specifies that a class that implements the generic Function interface will include a value()method that takes an argument of type A, and returns an argument of type B.
One important difference between Java generics and C++ templates is that in Java, generic declarations are compiled and type-checked into classes that can be reused directly, instead of expanded into additional classes.
That fact has several important advantages for the Java developer:
Compilation also reduces application size since the template isn't expanded into a different class each time it is used. Finally,
since generics are compiled into classfiles, the source code is not required to use a generic library. And since the compiled classes are totally backward compatible, they can be used with
code that employs older code like LinkedList()instead of LinkedList<Integer>().
After the presentation, Ed Hansen, a senior application architect for Vision Service Plan and an experienced C++ template developer, had this to say: "It's a great API. And the fact that generic declarations are compiled and type-checked instead of expanded at runtime (like C++ templates) makes debugging a lot easier."
Despite the fact that generics are not yet a standard part of the Java platform, the generics facility can still be used today! You can download and use the generic compiler with the resulting classes running in Java 1.4.
At the moment, the best way to use the generics facility is to create a generic API for your existing library classes. For example, you could add generic declarations to an interface, or you could copy a class, remove the code, and add generics to the resulting stub.
You would then compile the generic stub with the generic compiler and import that stub when you compile the code that uses
the library (with the generic compiler, once again). That code remains unchanged -- it still references List, for example, instead of List<Integer>.
When you compile, you turn on unchecked warnings, which warn you when a cast may not be valid -- a warning you cannot get today in any other way. However, you'll receive many meaningless warnings as well. It remains to be seen how easily you can filter out the warnings you don't care about so you can find the potential bugs in your app.
When you run the program, you run with the original classes -- the nongeneric versions. That way, you use the stable 1.4 versions at runtime, but you will have had the advantage of the additional compile-time error detection.