
Joshua Bloch
|
by Janice J. Heiss
May 8, 2003
With the scheduled beta release of Java 2 Platform, Standard Edition 1.5 (J2SE 1.5) in late 2003, (known as project "Tiger") there is quite a buzz among developers about coming changes. No one is better positioned to explain J2SE 1.5 language changes than Joshua Bloch, a senior staff engineer at Sun Microsystems, Inc. Bloch, an architect in the Core Java Platform Group, designed and implemented the award-winning Java Collections Framework, the java.math package, and has contributed to many other parts of the platform. The author of numerous articles and papers, Bloch has also written a book, Effective Java Programming Language Guide, which won the prestigious Jolt Award from Software Development Magazine. Bloch holds a Ph.D. in computer science from Carnegie-Mellon University. We met with him to look into the future of the Java programming language.
The improvements to J2SE being developed in project "Tiger" are intended to make programs clearer, shorter, safer, and easier to develop, without sacrificing compatibility. Could you tell us how the Java language will be easier to work with in J2SE 1.5?
|
"The new language features all have one thing in common: they take some
common idiom and provide linguistic support for it. In other words, they
shift the responsibility for writing the boilerplate code from the programmer
to the compiler."
- Joshua Bloch, senior staff engineer, Sun Microsystems
|
The new language features all have one thing in common: they take some common idiom and provide linguistic support for it. In other words, they shift the responsibility for writing the boilerplate code from the programmer to the compiler. Because the source code is now free of this boilerplate, it's easier to write and read. Because the compiler, unlike the programmer, never makes mistakes, the resulting code is also more likely to be free of bugs.
And the whole will be greater than the sum of its parts. All of the new features were designed with each other in mind. They interact harmoniously to provide a marked improvement in the expressiveness and safety of the language.
Which changes might be hardest for developers to adjust to? Or, what kinds of adjustments will they need to make?
I'm hopeful that none of the changes will be that hard for developers to adjust to. If I had to pick one that might require some adjustment, it would be generics, because you'll have to get used to providing additional information in declarations. Instead of merely saying:
List words = new ArrayList();
You'll have to say:
List<String> words = new ArrayList<String>();
The upside is that if you try to insert something that's not a string, you find out at compile time and fix the problem. Without generics, you discover such a bug when your most important customer calls your VP to tell him that the program on which his business depends just crashed with a ClassCastException.
The other upside is that you don't have to cast when you get an element out of the collection. So instead of:
String title = ((String) words.get(i)).toUppercase();
it's simply:
String title = words.get(i).toUppercase();
The beta release of J2SE 1.5 is scheduled for late 2003. These changes are all going through the Java Community Process (JCP). Two questions: How certain is it that the planned changes will actually be as currently conceived, and how can developers participate?
|
"I'm reasonably confident that the overall outlines of the proposals won't
change all that much between now and the final release."
- Joshua Bloch, senior staff engineer, Sun Microsystems
|
The expert groups for the relevant JSRs (14, 201, and 175) will certainly refine the draft proposals that are currently available, but I'm reasonably confident that the overall outlines of the proposals won't change much between now and the final release. Of course, the expert groups could prove me wrong.
The best way to participate is to read the drafts as they evolve, and to send comments to the relevant expert groups. The latest draft for generics can be found here: http://jcp.org/aboutJava/communityprocess/review/jsr014/.
You can download a pre-release compiler that understands generics and start getting your feet wet today: http://developer.java.sun.com/developer/earlyAccess/adding_generics/.
Early drafts of other language proposals are available on this page: http://www.jcp.org/en/jsr/detail?id=201. Look in Section 3.1, near the bottom of the page.
There's no draft available yet for JSR-175 (metadata) but there will be soon. Watch this space for further developments: http://www.jcp.org/en/jsr/detail?id=175
Do you want to give us a simple take-home message for each of the six areas of improvement?
I'll give it a whirl...
- Generics - Provides compile-time type safety for collections and eliminates the drudgery of casting.
- Enhanced
for loop - Eliminates the drudgery and error-proneness of iterators.
- Autoboxing/unboxing - Eliminates the drudgery of manual conversion between primitive types (such as
int) and wrapper types (such as Integer).
- Typesafe enums - Provides all the well-known benefits of the Typesafe Enum pattern (Effective Java, Item 21) without the verbosity and the error-proneness.
- Static import - Lets you avoid qualifying static members with class names, without the shortcomings of the Constant Interface antipattern (Effective Java, Item 17).
- Metadata - Lets you avoid writing boilerplate code, by enabling tools to generate it from annotations in the source code. This leads to a "declarative" programming style where the programmer says what should be done and tools emit the code to do it.
Moving to specifics and starting with generics, what is the difference between filtering a collection today and filtering a collection with generics?
Here's how you do it today:
/**
* Remove the four-letter words from the specified
* collection, which must contain only strings.
*/
static void expurgate(Collection c) {
for (Iterator i = c.iterator(); i.hasNext(); ) {
String s = (String) i.next();
if(s.length() == 4)
i.remove();
}
}
|
The casts aren't very pretty, and more importantly they can fail at run time. Suppose the user accidentally passes in a collection that contains string buffers rather than strings. The comment says that the client is required to pass in a collection of strings, but the compiler can't enforce the comment.
Here's how the same method looks with generics:
/**
* Remove the four-letter words from the specified collection of strings.
*/
static void expurgate(Collection<String> c) {
for (Iterator<String> i = c.iterator(); i.hasNext(); )
if (i.next().length() == 4)
i.remove();
}
|
Now it's clear from the method signature that the input collection must contain only strings. If the client tries to pass in a collection of string buffers, the program won't compile. And notice that the method doesn't contain any casts. It's one line shorter and, once you get used to reading generics, it's clearer, too.
Tell us about "enhanced for"?
Iterating over a collection is uglier than it needs to be. Most of the time when you iterate over a collection, you don't use the iterator for anything except getting elements. The "enhanced for" statement makes the compiler take care of the iterator for you. For example, here's a method that traverses a collection of timer tasks using an iterator:
void cancelAll(Collection c) {
for (Iterator i = c.iterator(); i.hasNext(); ) {
TimerTask tt = (TimerTask) i.next();
tt.cancel();
}
}
|
Now here's the same method with an "enhanced for" statement:
void cancelAll(Collection c) {
for (Object o : c)
((TimerTask)o).cancel();
}
When you read the statement out loud, the colon (:) is pronounced "in." It might have been more natural to use two new keywords, foreach and in, but new keywords are destabilizing. They break existing sources that used the new keywords as identifiers. We went out of our way to preserve compatibility when we specified the new language extensions.
Can you combine generics and the "enhanced for" loop?
Absolutely! Here's how the previous example looks when you add generics
into the mix:
void cancelAll(Collection<TimerTask> c) {
for (TimerTask task : c)
task.cancel();
}
I think this is really pretty! Now the program says exactly what it does, and provides compile-time type safety to boot.
Tell us about autoboxing.
As you know, the Java programming language has a "split type system": some types are primitives and others are object references. You can't put primitives into collections, so you end up converting back and forth between primitives (such as int) and "wrapper types" (such as Integer) when you need to store them in collections. Anyone who has done this can attest that it isn't pretty.
For example, take a look at this program, which generates a frequency table of the words found on the command line. It uses a Map whose keys are the words and whose values are the number of times that each word occurs on the line:
public class Freq {
private static final Integer ONE = new Integer(1);
public static void main(String args[]) {
// Maps word (String) to frequency (Integer)
Map m = new TreeMap();
for (int i=0; i<args.length; i++) {
Integer freq = (Integer) m.get(args[i]);
m.put(args[i], (freq==null ? ONE :
new Integer(freq.intValue() + 1)));
}
System.out.println(m);
}
}
|
Notice how messy the inner-loop code that increments the count looks? Now take a look at the same program rewritten with autoboxing, generics, and an enhanced for loop:
public class Freq {
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);
}
}
|
Sweet, huh? One thing worth noting: this program assumes that when you auto-unbox null, you get zero. It's still an open issue whether this will be the case. The alternative is to throw NullPointerException. Both alternatives have their advantages. Unboxing null to zero beautifies applications like the one above, but it can also sweep real errors under the rug. If anyone has any strong opinions, or better yet, convincing arguments on this issue, please pass them along to the JSR-201 expert group.
What are the advantages of the new typesafe enums over the standard approach, int enum pattern?
They're discussed in detail in Item 21 of my book, in connection with the
Typesafe Enum pattern. Very briefly:
- They provide compile-time type safety--int enums don't provide any type safety at all.
- They provide a proper name space for the enumerated type--with int enums you have to prefix the constants to get any semblance of a name space.
- They're robust--int enums are compiled into clients, and you have to recompile clients if you add, remove, or reorder constants.
- Printed values are informative--if you print an int enum you just see a number.
- Because they're objects, you can put them in collections.
- Because they're essentially classes, you can add arbitrary fields and methods.
Wow, that's powerful. How does the new typesafe enum language feature relate to the Typesafe Enum pattern in your book?
To a first approximation, the feature is simply linguistic support for the pattern. What took half a page of tedious error-prone code with the pattern now looks like the familiar C/C++ enum declaration:
enum Season { winter, spring, summer, fall }
But it doesn't act like a C/C++ enum! You get all of the cool features that I just mentioned. And it even fixes the major shortcoming of the Typesafe Enum pattern: you can use the new language feature in conjunction with switch statements.
Can you show me an example that demonstrates the more advanced capabilities of typesafe enums?
Sure, here's an enum that represents an American coin:
public enum Coin {
penny(1), nickel(5), dime(10), quarter(25);
Coin(int value) { this.value = value; }
private final int value;
public int value() { return value; }
}
|
See how the constant declarations invoke a constructor, passing in an int that represents the value in cents? And see how the value is stored in a private field with a public accessor? This just scratches the surface of what you can do.
Can you show me a class that uses the enum you just defined?
I was hoping you'd ask me that. Here's a little sample class that prints out a table of coins, their values, and their colors.
public class CoinTest {
public static void main(String[] args) {
for (Coin c : Coin.VALUES)
System.out.println(c + ": \t"
+ c.value() +"¢ \t" + color(c));
}
private enum CoinColor { copper, nickel, silver }
private static CoinColor color(Coin c) {
switch(c) {
case Coin.penny: return CoinColor.copper;
case Coin.nickel: return CoinColor.nickel;
case Coin.dime:
case Coin.quarter: return CoinColor.silver;
default: throw new AssertionError("Unknown coin: " + c);
}
}
}
|
See how we declare another enum for the colors? It doesn't cause any difficulty that there's a coin called nickel and a color called nickel, because coins and colors each get their own name space. Also, see how we can switch on enum constants? This is very useful when you want to "add a method" to an enum class, but you can't modify the class for whatever reason.
Looks good. What will the new static import facility offer developers?
It lets the programmer avoid prefixing static members with class names. People really want this, so much that they implement so-called constant interfaces to get the effect:
// "Constant Interface" antipattern - do not use!
public interface Physics {
public static final double AVOGADROS_NUMBER = 6.02214199e23;
public static final double BOLTZMANN_CONSTANT = 1.3806503e-23;
public static final double ELECTRON_MASS = 9.10938188e-31;
}
public class Guacamole implements Physics {
public static void main(String[] args) {
double moles = ...;
double molecules = AVOGADROS_NUMBER * moles;
...
}
}
|
This is a very bad idea: interfaces are for defining types, not providing constants. The fact that the Guacamole class uses the Physics constants is just an implementation detail, but it "leaks" into the public API of the Guacamole class. Not only does this confuse the clients of this class, but it creates a long-term commitment. Even if you re-implement the Guacamole class so that it doesn't need these constants, you still have to implement the interfaces, as clients of the Guacamole class can now depend on the fact that it implements Physics.
The static import facility offers a clean alternative. It lets the programmer avoid qualifying static member names without subtyping. It's analogous to the package import facility, except that it imports static members from a class, rather than classes from a package. Here's how it looks:
import static org.iso.Physics.*;
class Guacamole {
public static void main(String[] args) {
double molecules = AVOGADROS_NUMBER * moles;
...
}
}
|
Note that this works whether Physics is an interface or a class. If it just defines constants, it should definitely be a class rather than an interface.
Got it. Now could you tell us about the new metadata facility?
It's a bit different from the other features we've discussed. It's also focused on making life easier for the developer, but with the assistance of tools vendors.
These days, many APIs require a fair amount of boilerplate. For example, when you define a JAX-RPC Web Service you provide both an interface and an implementation class:
public interface CoffeeOrderIF extends java.rmi.Remote {
public Coffee [] getPriceList()
throws java.rmi.RemoteException;
public String orderCoffee(String name, int quantity)
throws java.rmi.RemoteException;
}
public class CoffeeOrderImpl implements CoffeeOrderIF {
public Coffee [] getPriceList() {
...
}
public String orderCoffee(String name, int quantity) {
...
}
}
|
This example was copied straight from our Web Services Tutorial.
With the metadata facility, you don't have to write all of this yourself. You just annotate the code to let a tool know which methods are remotely accessible, and the tool generates the above code. Here's how the source code looks with metadata:
import javax.xml.rpc.*;
public class CoffeeOrder {
@Remote public Coffee [] getPriceList() {
...
}
@Remote public String orderCoffee(String name, int quantity) {
...
}
}
|
All the boilerplate's gone!
Yes, that's much clearer. But you can't possibly define all of the useful attributes and build all the tools, can you?
No, JSR-175 is just providing the framework that enables others to define attributes and build tools. Other JSRs -- such as JSR-181, which is defining metadata for Web Services -- are defining attributes. We expect lots of activity in this area.
Looking further down the road, what future changes to the Java language do you anticipate?
It's hard to say. I'm too busy working on Tiger to think about what comes next.
Is there any message you want to leave us with?
|
"I've had the good fortune to play with early prototypes of the new language
features, and I find them a joy to use."
- Joshua Bloch, senior staff engineer, Sun Microsystems
|
Above all else, Tiger is a developer-focused release. When the Java programming language was introduced by James Gosling and his team, it took off like a rocket because it struck a chord with developers. This release is all about building on that legacy.
I've had the good fortune to play with early prototypes of the new language features, and I find them a joy to use. I have every confidence that other developers will too. The new features add more pleasure and productivity to what was already one of the most pleasurable and productive programming languages around.
See Also
J2SE 1.5 Language Changes at the JavaOne Conference
BOF
http://servlet.java.sun.com/javaone/sf2003/conf/bofs/display-3113.en.jsp
Session
http://servlet.java.sun.com/javaone/sf2003/conf/sessions/display-3072.en.jsp
J2SE 1.5 Java One Conference Session
http://servlet.java.sun.com/javaone/sf2003/conf/sessions/display-1540.en.jsp
Early Drafts Proposals for J2SE 1.5
http://www.jcp.org/en/jsr/detail?id=201
Draft for Generics
http://jcp.org/aboutJava/communityprocess/review/jsr014/
Pre-release Compiler Download
http://developer.java.sun.com/developer/earlyAccess/adding_generics/
J2SE
http://java.sun.com/j2se/
Effective Java Programming Language Guide
http://java.sun.com/docs/books/effective/
|