Channels ▼
RSS

Making the Grade


Making the Grade

Software Development

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.

Wildcards

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 the extends clause. The NumList method addAll provides an example:

The method 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 Double, moreNumbers can be bound to Double or any Double subclass.

Parameterized Methods

The class 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.

The new 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 T. The class that T resolves to must also implement the Comparable interface, which in turn must be bound to a superclass of List.

This is a significant capability: Under J2SE 1.4, you could pass a List containing 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.

Additional Bounds

The java.util.Collections class supports doing binary searches against a List. You can pass a List and a key Object to the static method binarySearch. For 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 signature. If T is bound to a Comparable type, under the covers, the signature of 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 type T in binarySearch can extend Object (thus preserving the signature of binarySearch) and (&) the appropriate Comparable interface.


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 like 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 T’s 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 List <?>[]).

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 method to 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.

Subversion

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 NumList and assign it to an instance of the bind type Integer, I get a ClassCastException—someone snuck a Double into my Integer collection!

Sun does provide a handful of wrapper methods in the Collections class, such as checkedList, that give you runtime protection from someone inserting a bad object into your collection. However, you must pass these methods a Class object 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!

Reflection

The reflection classes have been updated to support generics. The class Class understands parameterized types, so that executing getClass on an object will return a Class<T> object. Class has several methods to allow you to extract information on type parameters, including the type to which a class is bound. The classes Method and 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 a squared method:

In Integer, which extends Number, you could declare:

The benefit of covariance is similar to that of generics: Before, the Integer subclass would have had to return the abstract supertype Number, resulting in the need for Integer clients to cast whenever using the squared method. 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.

Lint Options

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 -Xlint:unchecked switch for more information. Doing so will supply you with the details on the violations, just as -deprecation does.

The -Xlint switch supports a number of switches for turning warnings on and off. Enter javac -X to get a listing of the switches available. Some of these are nice additions that check for things like fall-through switch statements and returning from a finally block, things you should not normally do. My initial recommendation is to turn all the switches on (-Xlint:all) and eliminate as many warnings as you can. My second recommendation is to use a more robust lint tool, such as Jlint or the Code Inspection tool found in IntelliJ IDEA.

—J.L.


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.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video