Sun’s goal for J2SE 1.5 is to make code “clearer, safer, sorter and easier to develop—without sacrificing compatibility.” Last month, I introduced the generics feature of J2SE 1.5, revealing how you can use parameterized types as well as construct your own. Do parameterized types achieve Sun’s goal? Here’s a report card:
Clearer: Client code is clearer—casts are eliminated; also, when you pass parameterized collections to methods, it’s obvious what type of object they contain. But parameterized type implementations can be far more abstruse, as you’ll see this month. Grade: B
Safer: A common defect in pre-1.5 Java involves stuffing objects of one type in a collection, and in a distant method, thinking the collection holds some other class of objects. Use of parameterized types can eliminate that possible confusion. However, it’s still possible to circumvent generics and get yourself in trouble. Grade: B
Shorter: Little difference overall. Grade: C
Easier to develop: Using parameterized types is easy, but you must fully understand the complexities of erasure in order to develop them. Also, the many limitations mean that it can be difficult to figure out how to solve certain problems. Grade: C
Retaining compatibility: The generics solution meets this goal. There were other possible solutions, but they would not have afforded the required level of compatibility. While there’s something to be said for not breaking existing code, I hope that at some point a major overhaul of Java eliminates its many little warts and flaws. Perhaps in Java 3. Grade: A
Overall grade: C+
This month, I’ll discuss advanced generics constructs and some limitations on the use of generics—the things that led to the not-so-hot report card.
Here’s some of the code for the
NumList type introduced last month:
Suppose you need to support adding a
NumList bound to one type to another
bound to the same type. A wildcard character,
?, as a bind parameter means
that it may be of
any type. You can also constrain a wildcard type using
extends clause. The
addAll provides an example:
addAll takes a
NumList as a parameter. This parameter can be bound
to any class that is a subclass of whatever
T resolves to. So, if the receiving
NumList is bound to
moreNumbers can be bound to
Double or any
java.util.Collections has been retrofitted with support for parameterized
collection types. The old
sort method has a simple signature:
The signature in 1.5 looks like:
Things are starting to get a bit involved! With generics, you’ll have to spend a bit more time before you can digest what a method signature is really saying.
sort method is generic—it lets you vary the type of the
list parameter. In 1.5, you can define a type parameter that applies only to
the following method. The 1.5
sort method takes a
List bound to a class
The class that
T resolves to must also implement the
which in turn must be bound to a superclass of
This is a significant capability: Under J2SE 1.4, you could pass a
anything to the
sort method. If you stuffed non-Comparable objects into the
list, you’d have to wait until runtime to discover the defect. Under
1.5, you’ll uncover your error at compile time.
java.util.Collections class supports doing binary searches against a
You can pass a
List and a key
Object to the static method
1.5, Sun wanted
binarySearch to solve the same problem of ensuring that the
list passed contains objects bound to the appropriate
Comparable type. An
initial workable solution might look like this:
(Expect more long multiline signatures under 1.5.)
The only problem is that this breaks compatibility, since it changes the method
T is bound to a
Comparable type, under the covers, the signature
binarySearch resolves to the following:
While this change wouldn’t affect most code, any code that presumed the exact signature (such as code using reflection) would now be broken. As with most new language releases, backward compatibility with existing code has always been a primary requirement for new Java releases.
Java 1.5 allows declaring additional bounds for type parameters. A type parameter
can extend a class or interface, plus any number of additional interface bounds.
The significant benefit in this case is that it allows the J2SE 1.5 API library
to maintain backward compatibility while adding a new constraint: The parameter
binarySearch can extend
Object (thus preserving the signature of
binarySearch) and (
&) the appropriate
Another potential use is to allow you to constrain a parameter to implementing two or more interfaces. However, this use would fall into the “questionable design” category. If I insist that a parameter object implement two interfaces, I declare the intent that my method will send messages defined in both interfaces to the parameter. This may violate good method and/or composition principles, and suggests that there is probably—but not always—a better design.
Most of us won’t need to worry about backward signature compatibility, and we should know better than to violate simple design principles, so most of us will have little need for additional bounds.
Limitations of Generics
Under C++, binding a parameterized type actually creates a new class, behind
the scenes, for each different type bound to. Anywhere a naked type variable
T appears in the source for the parameterized type, it would be replaced
with the bind type.
Erasure doesn’t create additional types. Java compiles a parameterized
type to a single class by replacing the naked type variable with
upper bounds (
Object by default).
The erasure scheme means that some things just don’t make sense, and
thus are disallowed. For example, constructing new objects of the naked type
is not possible. This would seem to make sense, since most of the time this
would equate to constructing new instances of either
Object or an abstract
type. Yet there are legitimate cases where you need to do so.
Some of the other limitations of generics in Java include: No naked types
in static variables, methods or static nested classes! No binding to primitive
types (but see next month’s discussion of autoboxing). No extending from
or implementing a naked type. No use of naked types in
instanceof, and no arrays
of generic types (but you can declare
In most cases, you can code around these restrictions, but having to do so
is enough cause for me to complain about erasure. As an example, one of the
coding patterns I appreciate is Joshua Kerievsy’s "Replace
Multiple Constructors With Creation Methods". Classes with multiple constructors don’t always
impart intent well, whereas I can create a static-side creation method with
a very explicit name. For example, I’d like to be able to add a creation
NumList that allows specification of its capacity:
Unfortunately, the compiler prevents me from doing so. I resent anything that diminishes my ability to make my code expressive.
I often encounter clever programmers who don’t want to go along with what I’ve coded. Once they get their hands on my code, they’ll do anything to subvert my intention, never mind the implications. Fortunately for them, and not for me, the generics implementation allows them to continue with their wily ways, as the following piece of code attests:
All it takes is a cast to the raw type,
NumList, and a nefarious programmer
can add any type to
nums—they can ignore the unchecked warning they’ll
receive. When code elsewhere in the system attempts to extract from the
and assign it to an instance of the bind type Integer, I get a
Double into my
Sun does provide a handful of wrapper methods in the
Collections class, such
checkedList, that give you runtime protection from someone inserting a bad
object into your collection. However, you must pass these methods a
to indicate the type you want to limit the collection to. From within a generic
class such as
NumList, there’s no way to determine the class a raw type
represents. This means you can’t effectively use checked collection methods
within a parameterized type. Stymied again!
The reflection classes have been updated to support generics. The class
understands parameterized types, so that executing
getClass on an object
will return a
Class has several methods
to allow you to extract information on type parameters, including the type
to which a
class is bound. The classes
Constructor also supply generics information.
The downside? You’re still limited by erasure and the fact that you
can’t obtain the
Class object to which a parameterized type is bound:
there’s no way to determine whether
NumList is bound to
Integer or to
Double. This has the potential to severely limit dynamic applications.
Covariant Return Types
A J2SE 1.5 modification to the Java language that’s exclusive of generics
is the addition of support for covariant return types: When overriding a method,
the subclass can vary the return type so that it’s a subclass of the
superclass return type. For example, suppose the class
Number were to declare
Integer, which extends
Number, you could declare:
The benefit of covariance is similar to that of generics: Before, the
subclass would have had to return the abstract supertype
in the need for
Integer clients to cast whenever using the
One wonders if covariance alone would have been sufficient, as a small bone,
until Sun is willing to drop backward compatibility.
Steep Curve Ahead
I’ve covered most of what you’ll need to know to use and implement parameterized types. There are still a few esoteric areas that most developers won’t need to concern themselves with; I refer you to Sun’s specification document for those concerns.
Overall, I believe that the J2SE 1.5 implementation of generics will help solve some problems. However, their creation represents an advanced topic for the average Java developer, and the learning curve will be steep. It also has the potential to be easily abused by the overly clever developer. Use these new Java stripes wisely, lest they bite you with higher maintenance costs.
Next month: The rest of the goodies.
When working with raw types (parameterized types not bound to anything), you
may receive an “unchecked” warning. As with deprecation warnings,
you get one warning by default, no matter how many transgressions appear
in your code. This is a good thing, because otherwise, your pre-1.5 code
could generate voluminous warnings. The compiler will tell you that you can
recompile and use the
Jeff Langr, owner of Langr Software Solutions, is the author of the book Essential Java Style and several articles, including two appearing in Software Development. He is currently working on a book on Java 1.5 and resides in Colorado Springs, Colo.