dcsimg

A Fresh Cup of Java

Version 1.5 of the Java platform, code named "Tiger," adds seven new features to the Java programming language. The unifying theme of these features is ease of development: make programs clearer, shorter, and safer, without sacrificing compatibility or compromising the spirit of the language.

Version 1.5 of the Java platform, code-named “Tiger,” adds seven new features to the Java programming language. Designed to complement one another, the unifying theme of the new features is ease of development: make programs clearer, shorter, and safer, without sacrificing compatibility or compromising the spirit of the language.

Loosely speaking, the new features of Java 1.5 provide linguistic support for common idioms, shifting the responsibility for writing boilerplate code from programmers to the compiler. Freed from boilerplate, code is easier to write, read, and maintain. And because the compiler is far less likely to err than programmers, the code is less likely to contain bugs.

Here’s what each feature does in a nutshell:

* Generics provide compile-time type safety for collections and eliminate the drudgery of casting. The result: stronger typing with less typing.

* A new for-each loop eliminates the toil and error-prone nature of using iterators and index variables in loops.

* Autoboxing/unboxing eliminates the travails of conversion between primitive types (such as int) and wrapper types (such as Integer).

* Enums provide a simple, powerful mechanism for representing enumerated types, such as the days of the week or suits of playing cards.

* Varargs let you pass a variable number of parameters to a method, without the usual effort of storing them in an array.

* Static import lets you avoid the drudgery of qualifying static members with class names without abusing interface inheritance.

* Metadata lets you avoid writing boilerplate code by enabling tools to generate it from annotations in the source code.

The whole of Java 1.5 is greater than the sum of its parts. Java’s new features were designed with each other and the rest of the language in mind. They interact harmoniously to provide a marked improvement in Java’s expressiveness and safety.

This article provides an introduction to the new features of Java 1.5 with the exception of metadata, which is the subject of a separate article in this issue (see page 26). Program examples illustrate how the features work together to simplify development, and tips describe how to and how not to use the new features. When you finish reading the article, you’ll be ready to download the Java 1.5 beta from http://java.sun.com/j2se/1.5.0/index.jsp and take it out for a spin.

Generics

When you take an element out of a Collection, you must cast it to the type of element that’s stored in the collection. Besides being inconvenient, casting is unsafe. The compiler doesn’t check that your cast is the same as the collection’s type, so the cast can fail at run time.

Generics provide a way for you to communicate the type of a collection to the compiler. Once the compiler knows the element type of the collection, the compiler can check that you use the collection consistently and can insert the correct casts on values being taken out of the collection.

Here is a simple example taken from the existing collections tutorial:


// Removes 4-letter words from c.
// Elements must be strings
static void expurgate(Collection c) {
for (Iterator i = c.iterator();
i.hasNext();)
if (((String) i.next()).length() == 4)
i.remove();
}

And here’s the same example modified to use generics:


// Removes the 4-letter words from c
static void expurgate(Collection
<String> c) {
for (Iterator <String> i =
c.iterator(); i.hasNext(); )
if (i.next().length() == 4)
i.remove();
}

When you see the code <Type>, read it as “of type.” So, the parameter declaration above reads as “collection of string c.”

The code using generics is clearer and safer. It eliminates an unsafe cast, and a number of extra parentheses. More important is the fact that part of the specification of the method has moved from a comment to its signature, so the compiler can verify at compile time that the type constraints are not violated at run time. Because the program compiles without a warning, we can state with certainty that it won’t throw a Class CastException at run time. The net effect of using generics, especially in large programs, is improved readability and robustness.

To paraphrase Generics Specification lead Gilad Bracha, when you declare c to be of type Collection <String>, it says something about the variable c that holds true wherever and whenever it’s used and the compiler guarantees it (assuming the program compiles without warning). A cast, on the other hand, conveys something the programmer thinks is true at a single point in the code, forcing the VM to check whether the programmer is right only at run time.

While the primary use of generics is collections, they have many other uses. “Holder classes,” such as WeakReference and ThreadLocal, have all been generified.

More surprisingly, Class has also been generified. Class literals now function as type tags, providing both run time and compile time typing.

This enables a style of static factories exemplified by the getAnnotation() method in the new AnnotatedElement interface:


<T extends Annotation> T
getAnnotation(Class<T> annotationType);

This is a generic method. It infers the value of its type parameter T from its argument, and returns an appropriate instance of T, as illustrated by the following snippet:


Author a = Othello.class.getAnnotation
(Author.class);

Prior to generics, you would have had to cast the result to Author. Also you would have had no way to make the compiler check that the actual parameter represented a subclass of Annotation.

Generics are implemented by type erasure: generic type information is present only at compile time, after which it is erased by the compiler. The main advantage of this approach is that it provides total interoperability between generic code and legacy code that uses non-parameterized types (which are technically known as raw types). The main disadvantages are that parameter type information isn’t available at run time, and that run time type safety is not guaranteed when interoperating with legacy code. There is, however, a way to achieve guaranteed run time type safety for collections.

The java.util.Collections class has been outfitted with wrapper classes that provide guaranteed run time type safety. The new wrappers are similar in structure to the synchronized and unmodifiable wrappers. These “checked collection wrappers” are very useful for debugging.

For example, suppose you have a set of strings, s, into which some legacy code has mysteriously inserted an integer. Without the wrapper, you won’t find out about the problem until you read the problem element from the set and an automatically generated cast to String fails. But, at that point, it’s too late.

If, however, you replace the declaration…


Set s = new HashSet();

with…


Set s = Collections.checkedSet(new
HashSet<String>(), String.class);

… the collection will throw a ClassCastException at the point where the legacy code attempts to insert the integer. The resulting stack trace should be enough to let you diagnose and repair the problem.

You should use generics everywhere you can. The extra effort in generifying code is well worth the gains in clarity and type safety. There is one caveat: you cannot use generics (or any other Tiger features) if you intend to deploy your compiled code on a pre-1.5 virtual machine.

If you’re familiar with C++’s template construct, you might think that Java’s generics are similar, but the resemblance is strictly superficial. C++ templates are best thought of as a macroprocessor on steroids. Java generics, on the other hand, are a bona fide extension to the type system. Unlike templates, generics do not cause code size to blow up, nor do they permit “template metaprogramming.”

There is much more to learn about generics. Take a look at Gilad Bracha’s “Generics Tutorial” at http://java.sun.com/j2se/1.5/pdf/generics-tutorial.pdf.

For-Each

Iterating over a collection is uglier than it needs to be. Consider the following method, which takes a collection of timer tasks and cancels them:


void cancelAll(Collection<TimerTask> c) {
for (Iterator<TimerTask> i =
c.iterator(); i.hasNext();)
i.next().cancel();
}

The iterator is just clutter. Furthermore, it’s an opportunity for error. The iterator variable occurs three times in each loop: that’s two chances to get it wrong.

JDK 1.5′s for-each construct gets rid of that clutter and the opportunity for error. Here’s how the example looks with for-each:


void cancelAll(Collection<TimerTask> c) {
for (TimerTask t : c)
t.cancel();
}

When you see the colon (:) read it as “in,” so the loop above reads “for each timer task t in c.” As you can see, the for-each construct combines beautifully with generics. It preserves all of the type safety, while removing the remaining clutter. Because you don’t have to declare the iterator, you don’t have to provide a generic declaration for it. (The compiler does this for you behind your back, but you needn’t concern yourself with it.)

Here’s a common mistake people make when they’re trying to do nested iteration over two collections:


List suits = …;
List ranks = …;
List sortedDeck = new ArrayList();

// BROKEN – throws NoSuchElementException!
for (Iterator i = suits.iterator();
i.hasNext(); )
for (Iterator j = ranks.iterator();
j.hasNext(); )
sortedDeck.add(new Card(i.next(),
j.next()));

Can you spot the bug? Don’t feel bad if you can’t. Both of the authors of this article have made this mistake. The problem is that the next() method is being called too many times on the “outer” collection (suits). It’s being called in the inner loop for both the outer and inner collections, which is wrong.

To fix it, you have to add a variable in the scope of the outer loop to hold the suit:


// Fixed, though a bit ugly
for (Iterator i = suits.iterator();
i.hasNext(); ) {
Suit suit = (Suit) i.next();
for (Iterator j = ranks.iterator();
j.hasNext(); )
sortedDeck.add(new Card(suit, j.next()));
}

So what does all this have to do with the for-each construct? It’s tailor-made for nested iteration! Feast your eyes:


for (Suit suit : suits)
for (Rank rank : ranks)
sortedDeck.add(new Card(suit, rank));

The for-each construct is also applicable to arrays, where it hides the index variable rather than the iterator. The following method returns the sum of the values in an int array:


// Returns the sum of the elements of a
int sum(int[] a) {
int result = 0;
for (int i : a)
result += i;
return result;
}

So when should you use the for-each loop? Any time you can. It really beautifies your code.

Unfortunately, you can’t use it everywhere. Consider, for example, the expurgate method shown earlier. The program needs access to the iterator to remove the current element. The for-each loop hides the iterator, so you can’t call remove(). Therefore, the for-each loop is not usable for filtering.

Similarly it’s not usable for loops where you need to replace elements in a list or array as you traverse it. Finally, it’s not usable for loops that must iterate over multiple collections in parallel. These shortcomings were known by the designers, who made a conscious decision to go with a clean, simple construct that would cover the great majority of cases.

Autoboxing

As any Java programmer knows, you can’t put an int (or other primitive value) into a collection. Collections can only hold object references, so you have to box primitive values into the appropriate wrapper class (which is Integer in the case of int). Then, when you take the object out of the collection, you get the Integer that you put in. So, if you need an int, you must unbox the Integer using intValue(). All of this boxing and unboxing is a pain, and clutters up your code. The autoboxing and unboxing feature automates the process, eliminating the pain and the clutter.

The following example illustrates autoboxing and unboxing, along with generics and the for-each loop. In a mere ten lines of code, it computes and prints an alphabetized frequency table of the words appearing on the command line.


// Print a frequency table of words on the
// command line
public class Frequency {
public static void main(String[] args) {
Map<String, Integer> m = new
TreeMap<String, Integer>();
for (String word : args) {
Integer freq = m.get(word);
m.put(word, (freq == null ? 1 :
freq + 1));
}
System.out.println(m);
}
}

$ java Frequency if it is to be it is up to me
{be=1, if=1, is=2, it=2, me=1, to=2, up=1}

The program first declares a map from string to integer, associating the number of times a word occurs on the command line with the word. Then it iterates over each word in the argument list. For each word, it looks up the work in the map. Then it puts a revised entry for the word into the map. The line that does this (m.put(word, (freq == null ? 1 : freq + 1))) contains both autoboxing and unboxing.

To compute the new value to associate with a word, first it looks at the current value (freq). If it’s null, this is the first occurrence of the word, so it puts 1 into the map. Otherwise, it adds 1 to the number of prior occurrences and puts that value into the map. But of course you can’t put an int into a map, nor can you add an int to an Integer. What’s really happening is this: to add 1 to freq, freq is automatically unboxed, resulting in an expression of type int. Since both of the alternative expressions in the conditional expression are of type int, so too is the conditional expression itself. Then, to put the int value into the map, it is automatically boxed into an Integer.

The result of all this magic is that you can largely ignore the distinction between int and Integer, with a few caveats:

* An Integer expression can have a null value. If your program tries to autounbox null, it will throw a NullPointer Exception.

* The == operator performs reference identity comparisons on Integer expressions and value equality comparisons on int expressions.

Finally, there are performance costs associated with boxing and unboxing, even if it’s done automatically.

Here’s another sample program featuring autoboxing and unboxing. It’s a static factory that takes an int array and returns a List of Integer backed by the array. In a mere ten lines of code this method provides the full richness of the List interface atop an int array. All changes to the list write through to the array and vice-versa. The lines that use autoboxing or unboxing are marked in italics.


// List adapter for primitive int array
public static List<Integer> asList(final
int[] a) {
return new AbstractList<Integer>() {
public Integer get(int i) {
return a[i]; }
// Throws NullPointerException if
// val == null
public Integer set(int i, Integer val) {
Integer oldVal = a[i];
a[i] = val;
return oldVal;
public int size() { return a.length; }
};
}

The performance of the resulting list is likely to be quite poor, as it boxes or unboxes on every get or set operation. It’s plenty fast enough for occasional use, but it would be folly to use it in a performance critical inner loop.

So when should you use autoboxing and unboxing? Use them only when there is an “impedance mismatch” between reference types and primitives, for example, when you have to put numerical values into a collection. It is not appropriate to use autoboxing and unboxing for scientific computing, or other performance-sensitive numerical code. An Integer is not a substitute for an int. While autoboxing and unboxing blur the distinction between primitive types and reference types, they don’t eliminate it.

Enums

Previous to release 1.5, the standard way to represent an enumerated type was like this:


// int enum Pattern – has severe problems!
public static final int SEASON_WINTER = 0;
public static final int SEASON_SPRING = 1;
public static final int SEASON_SUMMER = 2;
public static final int SEASON_FALL = 3;

This int enum pattern has many problems, such as:

* IT’S NOT TYPESAFE. Since a season is just an int, you can pass in any other int where a season is required, or add two seasons together (which makes no sense).

* THERE’S NO NAMESPACE. You must prefix constants of an int “enum” with a string (in this case SEASON) to avoid collisions with other int enum types.

* IT’S BRITTLE. Because int enums are compile-time constants, they are compiled into clients that use them. If a new constant is added between two existing constants or the order is changed, clients must be recompiled. If not, they’ll still run, but their behavior is undefined.

* PRINTED VALUES ARE UNINFORMATIVE. Because they’re just ints, if you print them out all you get is a number, which tells you nothing about what it represents, or even what type it is.

It was possible to get around these problems by using the Typesafe Enum pattern (described in Item 21 of Effective Java), but this pattern had problems, too. It was quite verbose, hence error prone, and enum constants could not be used in switch statements.

In Tiger, the Java programming language gets real support for enumerated types. In its simplest form, the enum construct looks just like the enum constructs in C, C++ and C#:


enum Season { WINTER, SPRING, SUMMER, FALL }

But appearances can be deceiving. Java’s new enum construct is far more powerful than the enum constructs in other languages, which are typically little more than glorified integers. Java’s new enum construct defines a full-fledged class (dubbed an enum type). In addition to solving all the problems mentioned above, Java’s enum allows you to add arbitrary methods and fields to an enum type, implement arbitrary interfaces, and more.

Here’s an example of a full-fledged playing card class built atop a couple of enum classes. The Card class is immutable, and only one instance of each Card is created, so it need not override equals() or hashCode().


public class Card implements Serializable {
public enum Rank { DEUCE, THREE, FOUR,
FIVE, SIX, SEVEN, EIGHT, NINE,
TEN, JACK, QUEEN, KING, ACE }

public enum Suit { CLUBS, DIAMONDS,
HEARTS, SPADES }

private final Rank rank;
private final Suit suit;
private Card(Rank rank, Suit suit) {
this.rank = rank;
this.suit = suit;
}

public Rank rank() { return rank; }
public Suit suit() { return suit; }
public String toString() { return rank
+ ” of ” + suit; }

private static final List<Card> protoDeck =
new ArrayList<Card>();
static {
for (Suit suit : Suit.values())
for (Rank rank : Rank.values())
protoDeck.add
(new Card(rank, suit));
}

public static ArrayList<Card> newDeck() {
return new ArrayList<Card>(protoDeck);
}
}

The Card class is serializable, implicitly relying on the serializability of the enum types Rank and Suit. All enum types are serializable, and the serial form is designed to withstand arbitrary changes in the enum type. The toString() method for Card takes advantage of the toString() methods for Rank and Suit. As you can see, the Card class is short (about 25 lines of code). If Suit and Rank had been built by hand, each of them would have been significantly longer than the entire Card class.

The (private) constructor of Card takes two parameters: a Rank and a Suit. When the one of the authors (OK, it was Josh) was writing this example, he accidentally reversed the order of the parameters in the constructor invocation. But because enums are typesafe, the compiler promptly informed him of his error and he was able to correct it in a matter of seconds. Contrast this to the int enum pattern, which would have caused the program to fail at run time.

The following example is a simple program called Deal that exercises Card. It reads two numbers from the command line, representing the number of hands to deal and the number of cards per hand.

Then it gets a new deck of cards, shuffles it, and deals and prints the requested hands.


public class Deal {
public static void main(String args[]) {
int numHands =
Integer.parseInt(args[0]);
int cardsPerHand =
Integer.parseInt(args[1]);
List<Card> deck = Card.newDeck();
Collections.shuffle(deck);
for (int i=0; i < numHands; i++)
System.out.println(deal(deck,
cardsPerHand));
}
public static <E> ArrayList<E>
deal(List<E> deck, int n) {
int deckSize = deck.size();
List<E> handView = deck.subList
(deckSize-n, deckSize);
ArrayList<E> hand = new
ArrayList<E>(handView);
handView.clear();
return hand;
}
}

$ java Deal 4 5
[FOUR of HEARTS, NINE of DIAMONDS, QUEEN
of SPADES, ACE of SPADES, NINE of SPADES]
[DEUCE of HEARTS, EIGHT of SPADES, JACK
of DIAMONDS, TEN of CLUBS, SEVEN of SPADES]
[FIVE of HEARTS, FOUR of DIAMONDS, SIX
of DIAMONDS, NINE of CLUBS, JACK of CLUBS]
[SEVEN of HEARTS, SIX of CLUBS, DEUCE of
DIAMONDS, THREE of SPADES, EIGHT of CLUBS]

Suppose you want to add data and behavior to an enum.

For example consider the planets of the solar system. Each planet knows its mass and radius, and can calculate its surface gravity and the weight of an object on the planet.

Here is how it would look (for brevity, only three planets are shown):


public enum Planet {
VENUS(4.8685e24, 6051.8e3),
EARTH(5.9736e24, 6378.1e3),
MARS(0.64185e24, 3397e3);

private final double mass; // in kilograms
private final double radius; // in meters
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
}
private double mass() { return mass; }
private double radius() { return radius; }

public static final double G = 6.67300E-11;
double surfaceGravity()
{ return G * mass / (radius * radius); }
double surfaceWeight(double otherMass)
{ return otherMass * surfaceGravity(); }
}

The enum type Planet contains a constructor, and each enum constant is declared with parameters to be passed to the constructor when it is created.

Here’s a sample program that takes your weight on earth (in any unit) and calculates and prints your weight on all of the planets:


public static void main(String[] args) {
double earthWeight =
Double.parseDouble(args[0]);
double mass = earthWeight/
EARTH.surfaceGravity();
for (Planet p : Planet.values()) {
System.out.printf
(“Your weight on %s is %f%n”,
p, p.surfaceWeight(mass));
}

$ java Planet 175
Your weight on VENUS is 158.420215
Your weight on EARTH is 175.000000
Your weight on MARS is 66.286793

The idea of adding behavior to enum constants can be taken one step further. You can give each enum constant a different behavior for some method. To do that, declare the method abstract in the enum class, and override it with a concrete method in each constant.

Here’s an example of an enum with constants that represent the four basic arithmetic operations and an eval() method that performs the respective operation:


public enum Operation {
PLUS {double eval(double x, double y)
{ return x+y;} },
MINUS {double eval(double x, double y)
{ return x-y;} },
TIMES {double eval(double x, double y)
{ return x*y;} },
DIVIDE{double eval(double x, double y)
{ return x/y;} };

// Do arithmetic op represented by this
// constant
abstract double eval(double x, double y);
}

And here’s a sample program that exercises the Operation class. It takes two operands from the command line, iterates over all the operations, and for each operation, performs the operation and prints the resulting equation:


public static void main(String args[]) {
double x = Double.parseDouble(args[0]);
double y = Double.parseDouble(args[1]);
for (Operation op : Operation.values())
System.out.printf(“%f %s %f = %f%n”,
x, op, y, op.eval(x, y));
}

$ java Operation 4 2
4.000000 PLUS 2.000000 = 6.000000
4.000000 MINUS 2.000000 = 2.000000
4.000000 TIMES 2.000000 = 8.000000
4.000000 DIVIDE 2.000000 = 2.000000

This is admittedly pretty sophisticated stuff, and many programmers will never need to use constant-specific methods, but it’s nice to know that they’re there if you need them.

Two classes — special-purpose Set and Map implementations called EnumSet and EnumMap — have been added to java. util to support enums. EnumSet is a high-performance Set implementation for enums. All of the members of an enum set must be of the same enum type. Internally, it’s represented by a bit-vector, typically a single long. enum sets support iteration over ranges of enum types.

For example given the following enum declaration…


enum Day { SUNDAY, MONDAY, TUESDAY,
WEDNESDAY, THURSDAY, FRIDAY, SATURDAY }

… you can iterate over the weekdays. The EnumSet class provides a static factory that makes it easy:


for (Day d : EnumSet.range(Day.MONDAY,
Day.FRIDAY)) System.out.println(d);

enum sets also provide a rich, typesafe replacement for traditional bit flags, such as EnumSet.of(Style.BOLD, Style. ITALIC).

Similarly, EnumMap is a high-performance Map implementation for use with enum keys, internally implemented as an array. enum maps combine the richness and safety of the Map interface with speed approaching that of an array.

So when should you use enums? Any time you need a fixed set of constants, including natural enumerated types (like the planets, days of the week, and suits in a card deck), as well as other sets where you know all possible values at compile time, such as choices on a menu, rounding modes, command line flags, and the like. It isn’t necessary that the set of constants in an enum type stay fixed for all time. The feature was specifically designed to allow for binary compatible evolution of enum types.

Variable Number of Arguments

In previous releases of Java, a method that takes an arbitrary number of values required you to create an array and put the values into the array prior to invoking the method.

For example, here is how you’d use the MessageFormat class to format a message:


Object[] arguments = {
new Integer(7),
new Date(),
“a disturbance in the Force”
};

String result = MessageFormat.format(
“At {1,time} on {1,date}, there was”
+ “{2} on planet”
+ “{0,number,integer}.”, arguments);

It’s still true that multiple arguments must be passed in an array, but the Java 1.5 varargs feature automates and hides the process. Furthermore, it is upward compatible with preexisting APIs.

So, for example, the MessageFormat.format() method now has this declaration:


public static String format(String
pattern, Object… arguments);

The three periods after the final parameter’s type indicate that the final argument may be passed as an array or a sequence of arguments. Varargs can be used only in the final argument position.

So, given the new varargs declaration for MessageFormat .format(), the above invocation may be replaced by the following shorter and sweeter invocation:


String result = MessageFormat.format(
“At {1,time} on {1,date}, there was +
“{2} on planet ”
+ “{0,number,integer}.”,
7 , new Date(),
“a disturbance in the Force”);

There is a strong synergy between varags and reflection, illustrated in the following program:


public static void main(String[] args) {
int passed = 0;
int failed = 0;

for (String className : args) {
try {
Class c = Class.forName(testName);
c.getMethod(“test”).invoke(c.newInstance());
passed++;
} catch (Exception ex) {
System.out.printf
(“%s failed: %s%n”, className, ex);
failed++;
}
}

System.out.printf(“passed=%d; failed=%d%n”,
passed, failed);
}

This little program is a complete, if minimal, test framework. It takes a list of class names on the command line, and for each class name, instantiates the class using its parameterless constructor and invokes the static method called test(). If the instantiation or invocation throws an exception, the test is deemed to have failed. The program prints each failure, followed by a summary of the test results.

The reflective instantiation and invocation no longer require explicit array creation, because the getMethod() and invoke() methods accept a variable argument list. The program reads much more naturally than it would without varargs.

So when should you use varargs? As a client, you should take advantage of them whenever the API offers them. Important uses in core APIs include reflection, message formatting, and the new printf() facility.

As an API designer, you should use them sparingly, only when the benefit is truly compelling. Generally speaking you should not overload a varargs method, or it will be difficult for programmers to figure out which overloading gets called.

Static Import

To access static members, it is necessary to qualify references with the class they came from. For example, one must say: double r = Math.cos(Math.PI * theta).

To get around this, people sometimes put static members into an interface and inherit from that interface. This is a bad idea. In fact, it’s such a bad idea that there’s even a name for it: the Constant Interface Antipattern (Effective Java, Item 17). The problem is that it’s a mere implementation detail that a class uses certain static members, but when a class implements an interface, it becomes part of the class’s public API. Implementation details should not affect public APIs.

The static import construct allows unqualified access to static members without extending the type containing the static members. Instead, the program imports the members, either individually via import java.lang.Math.PI or en masse with import java.lang.Math.*. Once the static members have been imported, they may be used without qualification, as in double r = cos(PI * theta).

The static import declaration is analogous to the normal import declaration. Where the normal import declaration imports classes, allowing them to be used without qualification, the static import declaration imports static members from classes, allowing them to be used without qualification.

So when should you use static import? Very sparingly! Only use it when you’d otherwise be tempted to declare local copies of the constants or to abuse inheritance.

If you overuse this feature, it can make your program unreadable and unmaintainable by polluting its namespace with all the static members you import. Readers of your code (including you a few months after you wrote it) won’t know which class a static member comes from.

If, however, you only import the statics from one or two classes, and you use them repeatedly throughout your program, static import can make your program more readable, removing the boilerplate of repetition of the class name.

Conclusion

Above all else, Tiger is a developer-focused release. When the Java programming language was introduced by James Gosling and his team in 1995, it took off like a rocket because it struck a chord with developers. This release is all about building on that legacy. We’ve had the good fortune to be able to use the Tiger features for the last year or so as we developed them. We find them a joy to use and we have every confidence that you will too. Enjoy!



Joshua Bloch is a Senior Staff Engineer at Sun Microsystems where he led the design and implementation of numerous Java platform features including the award-winning Java Collections Framework. He is the author of the bestselling, Jolt Award-winning “Effective Java Programming Language Guide.” He holds a Ph.D. from Carnegie-Mellon University and a B.S. from Columbia. Neal Gafter is a Senior Staff Engineer at Sun Microsystems where he is in charge of the Java compiler and language tools. He has worked in compilers since 1981. He holds a Ph.D. from the University of Rochester and a B.S. from Case Western.

Comments are closed.