Sweet and Simple



May 01, 2004
URL:http://www.drdobbs.com/sweet-and-simple/184415141

Software Development

Last month, I came down pretty hard on the new generics feature in Java 1.5. While I’m happy about some aspects of the generics implementation, it introduces a new level of complexity in the language that may cause as many headaches as it solves. This month, I’m thrilled to present a more upbeat report: The remainder of Java 1.5’s features—including enhanced for loops, autoboxing, varargs, typesafe enum, static import and metadata—are much easier to understand and use. I’ve used these features extensively in writing new code, and have found that they make my code simpler and more enjoyable to write.

Luxe Loops

One annoying aspect of working in Java is having to write the same code over and over again to iterate a collection of objects:

Granted, modern IDEs can make it as easy as a few keystrokes, but that’s still a lot of repetitive code. The need to cast each element is an additional nuisance (but one that Sun eliminated in 1.5 through the use of parameterized types).

Iteration in Java provides an opportunity for too much diversity. You’ll see code that mixes the use of Enumeration and Iterator objects. You’ll also see use of a while loop instead of the more idiomatic for loop. This inconsistency forces you to spend more time looking at the code, wondering why the original developer chose to do it that way.

Finally, I bet that many of you have made the mistake of accidentally calling the next method twice in a loop, resulting in a head-scratching bug-ferreting session.

The 1.5 enhanced for loop, also known as foreach, solves all these problems. It’s succinct, consistent and close to foolproof. To iterate a collection of employees, simply write:

You read this code as “For each Employee reference employee in employees.” You must bind the employees reference to the Employee type for this to work.

The foreach loop also supports iterating over arrays using the exact same syntax, which helps make your code more consistent. Of course, now you’ll see code using at least three different iteration techniques, but I don’t suppose Sun could have deprecated while and classic for loops.

You can use the foreach loop on any class that implements the new interface java.lang.Iterable. Sun has retrofitted the collection classes to implement this interface. To make your own class iterable via foreach, you implement the sole Iterable method iterator to return an Iterator<T> object.

Autoboxing—It’s Out of Sight!

Since one of Sun’s goals with 1.5 was to eliminate as much casting as possible, Java’s use of wrapper types was an obvious target. Currently, to stuff a primitive in a collection, you must first embed it in a wrapper object:

And, when extracting the int, you must write this ugly bit of code:

While generics now eliminate the need to cast, the introduction of autoboxing in 1.5 means that you no longer need to explicitly wrap or unwrap a primitive—Java automatically boxes the primitive in an appropriate wrapper object behind the scenes:

Similarly, Java automatically unboxes the primitive from the wrapper:

Ah, that’s much cleaner! Simpler, too! Java will wrap your primitives when it encounters a situation in which an object is required. However, you need to be aware that wrapping and casting still occur behind the scenes; otherwise, you could create performance problems.

A Pocketful of Parameters

The varargs (variable arguments) feature lets you define a method or constructor so that it takes a variable number of parameters for the last argument. For example, you might want to create a Department class that supports hiring one, two or many employees at a time:

You define the hire method in Department to take a variable number of arguments by supplying ellipses after the argument type:

Behind the scenes, Java wraps the multiple parameters into a single Employee array.

While you may not regularly build this feature into your own Java classes, you’ll probably use it very frequently. Sun has added a new class named java.util.Formatter that allows you to bind values to format strings, much like printf in C. Without the vararg support in 1.5, a printf method would have been unwieldy. But now you can code something like this:

The Formatter class has extensive support for formatting not only numerics and primitives, but date and time values, as well.

Ultimately, varargs is simple syntactic sugar that lets you shorten something like:

to:

It’s similar to autoboxing in this vein—not bad, but not earth-shattering, either. The Formatter class is a worthy use of this capability that can simplify cluttered System.out.println statements.

Avoiding Havoc with Typesafe Enum

Most Java developers declare a related set of constants by using discrete public static final references. The fundamental problem is that these constants are typically defined as String or int constants and thus are not typesafe. Suppose you define a Chess class:

Nothing prevents a user of this class from passing in the string “r” to the move method, which could wreak all sorts of havoc.

More conscientious developers use a pattern known as Typesafe Enum described by Joshua Bloch—one of the designers of the new 1.5 language features—in Effective Java (Addison-Wesley, 2001). To use this pattern, you create a new class to represent the constant type. You make the constructor private, and can thus provide a constrained list of enumerated constants.

Unfortunately, creating your own enumerated constant (enum) type is a bit of a hassle, and it can be tricky to do right. Few developers bother. Thankfully, the Typesafe Enum pattern is now built directly into Java. For the Chess example, you can define an enum type named Player:

Like any other Java type, this enum type goes into a source file named Player.java. You use the two constants it defines as you would use static references:


It’s impossible for a developer to pass anything but Player.WHITE or Player.BLACK to the move method.

The simple declaration of Player suggests that enum in Java might be like enum in C or C++. It’s not: In C, an enum represents a fixed series of integer values. In Java, an enum represents a fixed series of named objects.

Additionally, you can declare an enum to have methods, constructors and fields, just like any other Java class. The main restriction is that you can’t extend from an enum. Here’s what the enum for chess pieces might look like:

Each enum instance is created with a piece weighting that you can later extract.

Some of the other enum features:

Typesafe Enum is my favorite new feature in 1.5. It absolutely meets Sun’s goals to simplify coding and make things safer in Java.

Static Import

Bloch rails against the Constant Interface antipattern in Effective Java. Developers, including Sun itself in the Java library, often use interfaces to collect related constants. One of the perceived benefits is the elimination of the need to scope constant use with a type name. However, it’s an inappropriate use of interfaces: Among other problems, it exposes to clients the implementation detail that a class happens to use specific constants.

Sun introduced the static import feature largely to solve this problem. The basic idea is that you can “import” all methods and fields from a type; this absolves your code from the need to scope the member names. Client code is unaware of the fact that a class is importing the type.

The most fun I’ve had with the static import feature is when coding involved mathematical expressions. Before, to calculate a hypotenuse, you’d code:

With static import, you place an import static statement along with the rest of your import statements:

You then no longer need to scope the method calls with the Math class name:

You can also statically import single members (methods or functions) from a class:

While static import may be an occasional savior from onerous, excessive use of static members, it can be abused. Overusing it (for example, to avoid having to statically scope a single method call or constant) will quickly obscure the source of the static members.

Metadata: Neat, but Necessary?

Today, you can use javadoc tags to supply structured comments for your code. The tags are interpreted by the javadoc compiler to produce nicely formatted Web documents. J2SE 1.5 allows you to create new tags known as annotations that correspond to annotation types you define.

Annotations can be used to modify specific elements in your code: packages, fields, methods, types, parameters, constructors, local variables and other annotation types. The compiler checks to ensure that all annotations correspond to the annotation type declaration.

You might create an annotation type that lets developers mark code with “Todo” notes, such as the following annotation:

The corresponding annotation declaration:

An annotation declaration looks a lot like an interface declaration, except that the @ sign precedes the interface keyword. The members of the annotation type correspond to the keywords used in an annotation. A member’s value may be defaulted, in which case an annotation need not explicitly supply a value for that member.

The @Todo annotation type is itself annotated with meta-annotations that are defined in java.lang.annotation. The @Retention meta-annotation lets you specify how long an annotation will be retained; here, @Todo is retained by the virtual machine so that you can determine the annotations at runtime. The @Target meta-annotation restricts @Todo to only modifying methods.

Sun provides a few shortcut forms for annotations; for example, you can eliminate the keyword for an annotation whose annotation type contains the sole member value.

Annotation type members can return primitive values, String references, enum references, other annotation types or Class references. They may also return arrays of any of the preceding types. An annotation for a single-member annotation type declaring the member String[] value might look like:

You can extract annotations at runtime using Java reflection capabilities. The element types (for example, Method and Class) have been supplemented with new methods to aid you. This bit of code iterates the methods in class X and prints any @Todo annotations:

C# has an equivalent feature known as attributes. It has a bit more applicability in C#, as that language was designed with attributes in mind. For example, you mark a class as serializable in C# by using an attribute. Many of the potential uses in Java for annotations are already covered by existing language features, and for this reason, I had a difficult time coming up with many other good uses for annotations. The prototypical example is for designating methods to be exported as part of a Web service interface.

Another good example is a rewrite of the JUnit testing framework. JUnit currently depends on subclassing a TestCase class and on following a method-naming scheme; the rewrite lets you mark test classes and test methods using annotations. This scheme is currently used by the comparable .NET testing tool NUnit.

While the average developer probably won’t have a lot of use for annotations, it’s a powerful feature that allows for the creation and simplification of many useful Java tools.

Overall, I’m pretty happy with 1.5—see “Java 1.5: Report Card” for my final “grades.” The new features impart a significant change to the look and feel of Java code—a change that should appeal to both new and seasoned Java developers.

Java 1.5: A Report Card
Last month, I graded the single feature of generics, ranking how well it met Sun’s goals for 1.5 of making code “clearer, safer, shorter and easier to develop without sacrificing compatibility.” This month’s report card summarizes the remainder of the new language features. Check out the chart for my letter grades; read on for my additional comments. First, Sun deliberately avoided new keywords such as foreach and in. However, the sole new keyword in J2SE 1.5, enum, has the potential to break a lot of older code that uses the variable name enum for an Enumeration. Also, while some code can be made simpler with 1.5, there’s still the potential to obscure code using static import. As for metadata, since it provides additional Java functionality that was previously absent, it by definition increases the amount of code.

Language Features Clearer Safer Shorter Easier Compatibility
foreach A A A A A
autoboxing A A A A A
varargs A A A A A
typesafe enum A A A A C
static import C A A A A
metadata A A n/a A A

—Jeff Langr

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.