Experiences with the New Java 5 Language Features

by Jess Garms and Tim Hanson
Originally published on BEA Dev2Dev December 2005

Abstract

Java 5.0 is here, and many of you will be starting to use some of the new features added to this release of the JDK. Everything from the enhanced for loop to more complex features such as generics will soon start appearing in code that you write. We just completed a large Java 5.0-based assignment, and this article looks at our experiences with many of these features. It's not an introduction but a somewhat deeper examination of the features and how they'll affect you, along with a few tips on how to effectively use these features in your own projects.

Introduction

During the beta period for JDK 1.5, we worked on a Java 5 compiler for BEA's Java IDE. As we implemented various new features, people began exploiting them in new ways; some were clever and some were clearly candidates for a list of what not to do. The compiler itself used the new language features, so we gained direct experience in maintaining code using these features as well. This article is a look at many of these features and our experiences with them.

We expect that you are already familiar with the new features, so we don't provide a comprehensive introduction to each. Instead, we'll talk about some of the interesting, hopefully non-obvious implications and uses. These tips are a somewhat random collection of things we ran into, loosely grouped by language feature.

We'll start with the simplest features and work our way toward the most advanced ones. Generics is an especially rich subject and therefore occupies about half the article.

Enhanced for Loop

The new enhanced for loop provides a simple, consistent syntax for iterating over collections and arrays. A couple items are worth mentioning:

Init expression

The initialization expression is evaluated only once inside the loop. This means that you can often remove a variable declaration. In this example, we had to create an Integer array to hold the results of computeNumbers() to prevent reevaluation of that method on each pass through the loop. You can see the bottom code is a little cleaner than the above and doesn't leak the variable numbers:

Without Enhanced For:



int sum = 0;

Integer[] numbers = computeNumbers();

for (int i=0; i < numbers.length ; i++)

    sum += numbers[i];

With:



int sum = 0;

for ( int number: computeNumbers() )

    sum += number;

Limitations

Sometimes you need access to the iterator or index during iteration. Intuitively it seems like the enhanced for loop should allow this. It doesn't. Take the following example:



for (int i=0; i < numbers.length ; i++) {

    if (i != 0) System.out.print(",");

    System.out.print(numbers[i]);

}

We want to print out a comma-separated list of the values in the array. We need to know whether we're on the first item in order to know if we should print a comma. With enhanced for, there's no way to get at this information. We'd need to keep an index ourselves, or a boolean indicating whether we're past the first item.

Here's another example:



for (Iterator<integer> it = n.iterator() ; it.hasNext() ; )

    if (it.next() < 0)

        it.remove();


In this case, we want to remove the negative items from a collection of Integers. To do this, we need to call a method on the iterator, but when using the enhanced for loop, this iterator would be hidden from us. Therefore, we need to just use the pre-Java 5 method of iterating.

Note, by the way, that Iterator is generic, so the declaration is Iterator<Integer>. Many people seem to miss this and use Iterator in its raw form.

Annotations

Annotation processing is a large topic. We're not going to cover all the possibilities and pitfalls of it, as we're limiting our article to core language features.

We will, however, discuss the built-in annotations (SuppressWarnings, Deprecated, and Override) and limitations of annotation processing in general.

Suppress Warnings

This annotation turns off compiler warnings at a class or method level. Sometimes you know better than the compiler that your code must use a deprecated method, or perform some action that cannot be statically determined to be typesafe but in fact is:



@SuppressWarnings("deprecation")

public static void selfDestruct() {

    Thread.currentThread().stop();

}

This is probably the most useful of the built-in annotations. Unfortunately, javac doesn't support it as of 1.5.0_04. It is supported in 1.6, however, and Sun is working on back-porting it to 1.5.

The annotation is supported in Eclipse 3.1 and possibly other IDEs as well. This allows you to keep your code entirely warning-free. If a warning shows up when compiling, you can be certain that you just added it—helping to keep you aware of possibly unsafe code. With the addition of generics, this is even more desirable.

Deprecated

Unfortunately, Deprecated is a little less useful. It's meant to replace the @deprecated javadoc tag, but since it doesn't have any fields, there's no way to suggest to the user of a deprecated class or method what they should be using as a replacement. Most uses require both the javadoc tag and this annotation.

Override

Override indicates that the method it annotates should be overriding a method with the same signature in a superclass:



@Override

public int hashCode() {

    ...

}

Take the above example—if you were to fail to capitalize the "C" in hashCode, you wouldn't get an error at compile time, but at runtime, your method would not be called as you expected. By adding the Override tag, the compiler complains if it doesn't actually perform an override.

This also helps in the case where the superclass changes. If, say, a new parameter were added to this method and the method itself were renamed, then the subclass will suddenly fail to compile, as it no longer overrides anything in the super.

Other annotations

Annotations can be extremely useful in other situations. They work best for frameworks like EJB and Web services, when behavior is not directly modified, but rather enhanced, especially in the case of adding boilerplate code.

Annotations cannot be used as a preprocessor. Sun's design specifically precludes modifying the byte code of a class directly because of an annotation. This is so that the results of the language can be properly understood and tools such as IDEs can perform deep code analysis and functions like refactoring.

Annotations are not a silver bullet. When first encountered, people are tempted to try all sorts of tricks. Take this next proposal we got from someone:



public class Foo {

    @Property

    private int bar;

}

The idea here was to automatically create getter and setter methods for the private field bar. Unfortunately, this is a bad idea for two reasons: 1) it doesn't work, and 2) it makes this code harder to read and deal with.

It can't be done because, as we mentioned, Sun specifically precludes modifying the class that an annotation appears in.

Even if it were possible, it's not a good idea because it makes this code harder to understand. Someone looking at this code for the first time will have no idea that this annotation creates methods. Also, if in the future you need to do something inside one of those methods, the annotation is useless.

In summary, don't try to use annotations to do what regular code can do.