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:
- You can use
enum
constants to represent cases in a switch statement. - You can iterate all the
enum
constants by using thestatic values()
method on theenum
type. - You can override method definitions for each
enum
constant, creating polymorphicenums
.
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.