 | Level: Intermediate Brian Goetz (brian.goetz@sun.com), Senior Staff Engineer, Sun Microsystems
27 Feb 2007 Java™ 5 added a number of powerful language features: generics, enumerations, annotations, autoboxing, and the enhanced for loop. However, many shops are still tied to JDK 1.4 or earlier and may be for some time to come. But it's still be possible for those developers to take advantage of these powerful language features while continuing to deploy on earlier JVMs. Brian Goetz returns from his hiatus in this installment of Java theory and practice to demonstrate how.
With Java 6.0 being newly released, you might think the Java 5
language features are "old news." But even now, when I ask developers
which version of the Java platform they are using in development,
typically only half are using Java 5 -- and the other half
are jealous. They are eager to use the language features added in Java
5 such as generics and annotations, but a number of factors still
prevent many from doing so.
One category of developers unable to take advantage of Java 5
features are those who develop components, libraries, or application
frameworks. Because their customers may still be using JDK 1.4 or
earlier and classes compiled with Java 5 cannot be loaded by JDK
1.4 or previous JVMs, using Java 5 language features would limit their
customer base to companies that have already transitioned to Java 5.
Another group of developers often held back from using Java 5 are those working with Java EE. Many shops will not use Java 5 with Java EE 1.4 or earlier for fear that it will not be supported by their application server vendor. But it may be a while before those shops transition to Java EE 5. In addition to the lag time between the Java EE 5 and Java SE 5 specifications, commercial Java EE 5 containers are not necessarily available as soon as the ink is dry on the specification, businesses do not necessarily upgrade their applications servers as soon as the next version is available, and even after upgrading their application server, it may take some time to certify their applications on the new platform.
Java 5 language feature implementation
The language features added in Java 5 -- generics, enumerations,
annotations, autoboxing, and the enhanced for loop -- required no
change to the JVM instruction set, and are almost entirely implemented
in the static compiler (javac) and class libraries. When
the compiler encounters the use of generics, it attempts to verify
that type safety is preserved (emitting an "unchecked cast" warning if
it cannot) and then emits bytecode that is identical to what would be
produced from equivalent nongeneric code, casts and all. Similarly,
autoboxing and the enhanced for loop are simply "syntactic sugar" for
equivalent, but more verbose, idioms, and enumerations are compiled
into ordinary classes.
In theory, you could take the class files produced by javac and load
them in earlier JVMs, which was, in fact, the intention when JSR 14
(the Java Community Process working group responsible for generics)
was convened. However, other issues (such as retention of annotations)
forced the class file version to be changed between Java 1.4 and Java
5, which prevents code compiled for Java 5 to be loaded by earlier
JVMs. Further, some of the language features added in Java 5 have
dependencies on the Java 5 libraries. If you compile a class with
javac -target 1.5 and try to load it on an earlier JVM,
you'll get an UnsupportedClassVersionError because the -target
1.5 option generates classes with a class file version of 49,
and JDK 1.4 only supports class file versions through 48.
The for-each loop
The enhanced for loop, sometimes called the for-each loop, is
translated by the compiler as if the programmer had supplied the
equivalent old-style for loop. The for-each loop can iterate over the
elements of an array or of a collection. Listing 1 shows the syntax of
iterating over a collection with the for-each loop:
Listing 1. The for-each loop
Collection<Foo> fooCollection = ...
for (Foo f : fooCollection) {
doSomething(f);
}
|
The compiler translates this code into the equivalent
iterator-based loop, as shown in Listing 2:
Listing 2. Iterator-based equivalent for Listing 1
for (Iterator<Foo> iter=f.iterator(); f.hasNext();) {
Foo f = (Foo)iter.next();
doSomething(f);
}
|
How does the compiler know that the supplied argument has an
iterator() method? The architects of the
javac compiler could have built in understanding of the
collections framework, but this approach would have been unnecessarily
restrictive. Instead, a new interface was created,
java.lang.Iterable (see Listing 3), and the collection
classes were retrofitted to implement Iterable. This way,
container classes that do not build on the core collections framework
can still take advantage of the new for-each loop. But doing so
creates a dependency on the Java 5 class library because
Iterable is not present in the JDK 1.4 library.
Listing 3. The Iterable interface
public interface Iterable<T> {
Iterator<T> iterator();
}
|
Enumerations and autoboxing
Just like the for-each loop, enumerations require support from the
class library. When the compiler encounters an enumerated type, it
generates a class that extends the library class
java.lang.Enum. But, just like Iterable, the
Enum class is not present in the JDK 1.4 class library.
Similarly, autoboxing relies on the
valueOf() methods being added to the primitive wrapper classes
(such as Integer). When boxing requires conversion from
int to Integer, rather than calling
new Integer(int), the compiler generates a call to
Integer.valueOf(int). The implementation of the
valueOf() methods employs the flyweight pattern to
cache the Integer objects for commonly used integer
values (the Java 6 implementation caches integers from -128 to 127),
which may improve performance by eliminating redundant
instantiations. And, just like Iterable and
Enum, the valueOf() methods are not present
in the JDK 1.4 class library.
Varargs
When the compiler encounters a method defined with a variable-length
argument list, it converts it into a method that takes an array of the
appropriate component type; when the compiler encounters a call to a
method with a variable-length argument list, it boxes the arguments
into an array.
Annotations
When an annotation is defined, it can be annotated with
@Retention, which determines what the compiler will do
with classes, methods, or fields that possess that annotation. The
defined retention policies are SOURCE (discard annotation
data at compilation), CLASS (record annotations in the
class file), or RUNTIME (record annotations in the class
file and retain them at runtime so they can be accessed reflectively).
Other library dependencies
Prior to Java 5, when the compiler encountered an attempt to
concatenate two strings, it used the helper class
StringBuffer to perform the concatenation. In Java 5
and later, it instead generates calls to the new
StringBuilder class, which is not present in the JDK 1.4
and earlier class libraries.
Accessing Java 5 features
Because of dependencies of language features on library support, even
if the class files produced by the Java 5 compiler could be loaded
by earlier JVM versions, execution would still fail because of class
loading errors. However, it should be possible to solve these problems
by suitably transforming the bytecode because these missing classes
do not contain substantial new functionality.
JSR 14
During the development of the Java generics specification (and other
language features added in Java 5), experimental support was added
to the javac compiler to allow it to consume Java 5
language features and generate bytecode that could be run on a Java
1.4 JVM. While these features are not supported (or even documented),
they are used by a number of open source projects to allow developers
to code using Java 5 language features and produce JAR files that
can be used on earlier JVMs. And, now that javac is
open source, it is possible the features might be supported by a third
party. To activate these features, you can invoke javac with the
-source 1.5 and -target jsr14 options.
The JSR 14 target mode of javac causes the compiler to emit JDK
1.4-compatible bytecode corresponding to Java 5 language features:
- Generics and varargs: The casts inserted by the compiler in the
presence of generics have no dependency on the class library, and so they
can execute equally well on a pre-5 JVM. Similarly, the code
generated by the compiler in the presence of variable-length argument
lists has no dependency on the class library.
for-each loop: When iterating over an array, the compiler
generates an induction variable and the standard array iteration
idiom. When iterating over a Collection, the compiler
generates the standard iterator-based idiom. When iterating over a
non-Collection Iterable, the compiler produces an
error.
- Autoboxing: Rather than generating calls to the
valueOf() method in the wrapper class, the compiler
generates calls to the constructor instead.
- String concatenation: The JSR 14 target mode of
javac causes the
compiler to generate calls to StringBuffer instead of
StringBuilder.
- Enumerations: The JSR 14 target mode of
javac has no
special support for enumerations. Code that attempts to use
enumerations will fail with a NoClassDefFoundError
looking for the java.lang.Enum base class.
Using the JSR 14 target mode allows you to write code that uses
generics, autoboxing, and the for-each loop in the "easy" cases, which
may suffice for many projects. It is convenient, if unsupported, and
the compiler generates mostly compatible bytecode in a single pass.
Retroweaver
There are certain Java 5 language features not supported by the
JSR 14 target mode (such as Iterable and enumerations). An
alternate approach, taken by open-source projects such as Retroweaver
and Retrotranslator, is to generate bytecode using -target
1.5 and then mechanically transform the bytecode into
something compatible with JDK 1.4.
Retroweaver came first, and it handled all the cases handled by javac -target JSR 14, and a few more:
- The
for-each loop: Retroweaver provides an implementation of the
Iterable interface and rewrites classes that implement
Iterable to implement its own version instead.
- Autoboxing: Retroweaver rewrites calls to the
valueOf() methods to the corresponding constructors.
- String concatenation: Retroweaver replaces use of
StringBuilder with StringBuffer.
- Enumerations: Retroweaver provides an implementation of the
Enum base class and rewrites classes that implement
Enum or invoke its methods to use its own version
instead.
Retrotranslator
As often happens in the open source world, if a project stops moving
forward, it is declared dead and a new project takes its place -- even
if it is just resting. Such was the fate of Retroweaver; the primary
maintainer took a break from the project and another similar
project, Retrotranslator, took its place. Retrotranslator offers the
same features as Retroweaver, plus many additional features aimed at
supporting important class library additions in Java 5:
- Replaces calls to
java.util.concurrent classes to
corresponding classes in the open-source JDK 1.4 backport.
- Provides implementations for features added to the collections
framework in Java 5, such as the new methods in
Arrays
and Collections. Similarly, provides implementations of
other new methods and classes added to the class library in Java
5.
- Supports runtime reflection for annotations.
Both Retroweaver and Retrotranslator can perform their bytecode
transformation statically (at compile time) or dynamically (at
class-load time).
Summary
For the unlucky developers constrained from using Java 5
language features -- and there are still a lot of them, unfortunately
-- there are several options that allow you to use some of these
features and retain bytecode compatibility with JDK 1.4 and
earlier. There is the unsupported -target jsr14 option on
javac that generates JDK 1.4-compatible bytecode for some
Java 5 language features and the open-source Retroweaver and
Retrotranslator projects that transform most Java 5 bytecode into
Java 1.4-compatible bytecode. Whichever you choose, of course, don't
forget to test extra carefully to verify true compatibility!
Resources Learn
Get products and technologies
- Download Retroweaver: Take advantage of the new Java 1.5 language features while still retaining total binary compatibility with 1.4 virtual machines.
- Download Retrotranslator: Open source tool that translates Java classes compiled with JDK 5 into classes that can be run on JVM 1.4.
Discuss
About the author  | |  | Brian Goetz has been a professional software developer for 20 years. He is a Senior Staff Engineer at Sun Microsystems, and he serves on several JCP Expert Groups. Brian's book, Java Concurrency In Practice, was published in May 2006 by Addison-Wesley. See Brian's published and upcoming articles in popular industry publications. |
Rate this page
|  |