Technical Article
More Effective Java With Google's Joshua Bloch
By Janice J. Heiss, October 2008
Joshua Bloch, Google's chief Java architect, is a former Distinguished Engineer at Sun Microsystems, where he led the design and implementation of numerous Java platform features, including JDK 5.0 language enhancements and the award-winning Java Collections Framework. He holds a Ph.D. in computer science from Carnegie-Mellon University.
Bloch won the prestigious Jolt Award from Software Development Magazine for the first edition of his 2001 book, Effective Java Programming Language Guide , known to many developers as Effective Java , and he is also the coauthor (with Neal Gafter) of the highly regarded book Java Puzzlers.
If there's one book that Java developers repeatedly refer to as their favorite, it's Effective Java.
Q: In the preface to the new edition of Effective Java , you described moving to Google in 2004 and wrote: "I've also had the pleasure of using the Java platform to develop libraries for use within Google. Now I know what it feels like to be a user." How did your experience as a user inform the new edition of the book?
A: Well, for one thing, it reinforced my feeling that it's critical to get API designs right. For example, I found myself wanting to provide an alternative FileChannel
implementation for the Google File System, but I couldn't, because FileChannel
is a concrete class rather than an interface. Similarly, I wanted to implement a growable ByteBuffer
. These are the sort of frustrations that ordinary developers feel every day.
Q: You have said that a common fault among Java developers is the natural tendency to optimize code, resulting in slower code that is needlessly complicated. Why do developers mistakenly optimize code?
"It's easy to feel like the general warnings about premature optimization don't apply to you, because you just know which code is time-critical and how to make it fast. But no one can determine this without measuring before and after each attempted optimization."
Joshua Bloch Author, Effective Java, Second Edition
A: To be a software developer, you have to be an optimist -- otherwise, it would feel like a losing battle. Generally, this is a good thing, but it has a downside: Optimism can lead to overconfidence. It's easy to feel like the general warnings about premature optimization don't apply to you, because you just know which code is time-critical and how to make it fast. But no one can determine this without measuring before and after each attempted optimization.
Q: Another fault you refer to involves developers writing their own code when perfectly good libraries exist. Why do developers do this?
A: Two reasons: By far the most common reason is that the developer doesn't know the library exists. I feel for that developer, because there are so many libraries out there that it's impossible to keep track of them all. That said, it's so hard to get some of these facilities right that it's worth making the effort to find out if a library exists.
This is particularly true where concurrency is involved. It's not uncommon for experts to spend literally months writing apparently modest concurrency utilities. When faced with a need for this sort of functionality, the wise developer will do whatever it takes to find an appropriate library. It's so easy to get nontrivial concurrent code wrong, and the resulting bugs can be nearly impossible to detect.
The second reason that developers tend to reinvent the wheel is the same reason they tend to optimize prematurely: In order to stay sane, most developers have a can-do attitude, and some take it too far. They say to themselves, "Yes, there's a library, but I can do better." Maybe you can, but that doesn't mean you should. Use the standard library unless it's profoundly unsuited to your needs.
Ignoring Libraries
"In order to stay sane, most developers have a can-do attitude, and some take it too far. They say to themselves, 'Yes, there's a library, but I can do better.' Maybe you can, but that doesn't mean you should."
Joshua Bloch Author, Effective Java, Second Edition
Q: George Orwell famously presented five rules of good writing -- and then added a sixth: "Break any of these rules sooner than say anything outright barbarous." Effective Java now consists of 78 items with titles consisting of short rules like "Favor generic methods" or "Consider using a custom serialized form," to choose two at random. Do you have any favorite rules? And when should developers break the rules?
Joshua Bloch Signing Effective Java at Jazoon
A: First, I should point out that I shamelessly stole Orwell's admonition. In the introduction to Effective Java, it says, "You should not slavishly follow these rules, but violate them only occasionally and with good reason."
I can be somewhat fickle, so my favorite rules might change if you ask me on another day, but today I'll go with Item 13, "Minimize the accessibility of classes and members," and Item 15, "Minimize mutability." Both of these rules transcend any particular programming language.
The first tells you to hide information to the maximum extent feasible. This principle, due originally to David Parnas, is one of the most fundamental tenets of good programming. If you hide information, you are free to change it without risking harm to the system. Information hiding decouples the components of the system, allowing them to be developed, tested, optimized, used, understood, and modified in isolation.
Item 15 tells you to keep the state space of each object as simple as possible. If an object is immutable, it can be in only one state, and you win big. You never have to worry about what state the object is in, and you can share it freely, with no need for synchronization. If you can't make an object immutable, at least minimize the amount of mutation that is possible. This makes it easier to use the object correctly.
As an extreme example of what not to do, consider the case of java.util.Calendar
. Very few people understand its state-space -- I certainly don't -- and it's been a constant source of bugs for years.
So when should you break the rules? To paraphrase Orwell, you should break them when they'd result in code that is "outright barbarous." As a simple example, Item 25 says, "Prefer lists to arrays." So why does the values
method present on every enum type return an array? Because the most common use of this method is to iterate over the elements of its return value, and it's much cheaper to iterate over an array than any List
implementation. With the for-each
construct, the code to iterate is the same either way:
for (Planet p : Planet.values())
You might think this was premature optimization on the part of the enum designers, but it wasn't. This isn't just inner loop code, it is the (inner) loop
. Of course, we (the JSR 201 expert group) did extensive performance measurements before making this decision, and I'm satisfied that we made the right decision.
Another time that you should break the rules is when two of the rules would drive you to opposite decisions. In such cases, you're forced to violate at least one of the rules, and you have to choose which is more important, which can be very difficult.
When faced with such decisions, I typically discuss it with someone else for a sanity check. There's no shame in doing this, and it's arguably a best practice. Contrary to popular belief, software design is not -- or should not be -- a solitary occupation.
Generics, Enums, and Annotations
Q: Give us a taste of what you believe developers should know about generics, enums, and annotations.
A: Owing to space limitations, it has to be a small taste, but here goes.
For generics, the sound bite is "Don't use raw types" (Item 23). If a library designer took the time to write a generic library, you should take advantage of it. In other words, don't do this:
// Raw type-leads to unsafe code!
List dogs = new ArrayList();
List<Dog> dogs = new ArrayList<Dog>();
"For generics, the sound bite is 'Don't use raw types.' ... If a library designer took the time to write a generic library, you should take advantage of it."
Joshua Bloch Author, Effective Java, Second Edition
At first, this may seem like needless verbiage, but it's not. By telling the compiler what kind of elements the list contains, you enable it to find many errors at compile time that would otherwise cause a ClassCastException
at runtime. You also eliminate ugly casts from your program.
For enums, the sound bite is "Always use enums in place of int
constants" (Item 30). Enums provide so many advantages: compile-time type safety, the ability to add or remove values without breaking clients, meaningful printed values, the ability to associate methods and fields with the values, and so on. Since we have EnumSet
, this advice applies equally to bit fields, which should be considered obsolete.
For annotations, the sound bite is "Don't define your own unless you have a very good reason, but do use the standard ones for your environment." For many programmers, the only ones that matter will be @Override
(Item 36) and @SuppressWarnings
(Item 24). Using the @Override
annotation is an easy way to save yourself from errors that would otherwise be very hard to detect. Notably, it is not uncommon to accidentally overload the equals
method when you intend to override it, causing subtle, pernicious errors:
// Broken! Unintentional overloading
public boolean equals(MyClass other) { // MyClass should be Object.
...
}
If you tell the compiler that you believe you're overriding a superclass
method, it will inform you of your error:
// @Override annotation prevents broken code from compiling.
@Override public boolean equals(MyClass mc) {
...
}
Best Practices for Lazy Initialization
Q: What's most important to understand about best practices for lazy initialization?
A: The single most important piece of advice is "Don't do it unless you need to." The great majority of your initialization code should look like this:
// Normal initialization, not lazy!
private final FieldType field = computeFieldValue();
If you need lazy initialization for correctness -- but not for performance -- just use a synchronized accessor. It's simple and clearly correct.
If you need better performance, your best choice depends on whether you're initializing a static field or an instance field. If it's a static field, use the lazy initialization holder class idiom:
// Lazy initialization holder class idiom for static fields
private static class FieldHolder {
static final FieldType field = computeFieldValue();
}
static FieldType getField() { return FieldHolder.field; }
This idiom is almost magical. There's synchronization going on, but it's invisible. The Java Runtime Environment does it for you, behind the scenes. And many VMs actually patch the code to eliminate the synchronization once it's no longer necessary, so this idiom is extremely fast.
If you need high-performance lazy initializing of an instance field, use the double-check idiom with a volatile field. This idiom wasn't guaranteed to work until release 5.0, when the platform got a new memory model. The idiom is very fast but also complicated and delicate, so don't be tempted to modify it in any way. Just copy and paste -- normally not a good idea, but appropriate here:
// Double-check idiom for lazy initialization of instance fields.
private volatile FieldType field;
FieldType getField() {
FieldType result = field;
if (result == null) { // First check (no locking)
synchronized(this) {
result = field;
if (result == null) // Second check (with locking)
field = result = computeFieldValue();
}
}
return result;
}
For more information on this topic, see Item 71.
Q: Tell us something that you are really proud of in the second edition of Effective Java.
A: I'm proud that I was able to preserve the feel of the book, even as the language grew. One reason the first edition was so successful is that it was small and approachable. The addition of all the new language and library features made the platform larger and more complex. Inevitably, the book grew (from 57 items to 78), but I tried really hard to keep it short and clear. Based on early feedback, I think I was largely successful.
The Strangest Thing About the Java Platform
Q: After having seven more years to reflect on the development of the Java platform, what's the strangest thing you can say about it?
A: Ooh, good question... I'm going to say that the strangest thing about the Java platform is that the byte type is signed. I've never heard an explanation for this. It's quite counterintuitive and causes all sorts of errors. For example, what do you think this program does?
class Delight {
public static void main(String[] args) {
for (byte b = Byte.MIN_VALUE;
b < Byte.MAX_VALUE; b++) {
if (b == 0x90)
System.out.print("Joy!");
}
}
}
If you have any doubts, run it. If you need an explanation for the behavior, find a copy of Java Puzzlers and look at Puzzle 24.
Unit Tests Are Essential
Q: Java Champion Dr. Heinz Kabutz finds that failure to unit test is a big problem among Java developers. He reports, "At conferences, I ask, 'How many of you have unit tests for your code?' Almost no one raises their hands -- and these are experienced professionals." Your reaction?
A: I'm really sorry to hear it. Unit tests are essential! If you don't have them, you don't know whether your code works. Having a good set of unit tests gives you much more confidence that your code works in the first place and that you don't introduce bugs as you maintain it. Inevitably, you will introduce bugs, but your unit tests will often let you find the bugs as soon as you introduce them, so you can fix them before they cause any damage.
Note, however, that unit tests are not sufficient to ensure that your code works. You need to understand the code and to prove to yourself that it works. And you need to have someone else review it. When it comes to code, two heads really are better than one. For a more detailed discussion of this topic, see my binary search article.
ReadResolve
Isn't All It's Cracked up to Be
Q: When you revised Effective Java, you said that you had to critically examine everything in the first edition. Was there anything you originally wrote that you realized was misguided?
A: Yes. For one thing, I used to think that a readResolve
method reliably guaranteed that a singleton remained a singleton in the face of serialization and deserialization. It turns out that this isn't quite true. (See Item 77.)
Luckily, there's a nice solution: implement your serializable singleton as a single-element enum. Also, I discovered various minor embarrassments as I went over the first edition with a fine-toothed comb, in the form of typos that eluded detection for seven years. Luckily, I don't remember any of them right now.
Become a Better Writer
Q: You have said that developers should read Strunk and White's book The Elements of Style because being a better writer will make you a better developer. Can you explain why this is so?
A: I believe that reading Strunk and White will make you a better developer because good programming and good writing are both about clarity and economy of expression. You can't write good code or good prose unless you understand what it is you're trying to say. Many of Strunk and White's admonitions have direct analogues for software. For example, Strunk and White say, "Omit needless words!" where Andy Hunt and Dave Thomas ( "The Pragmatic Programmers") say, "Don't repeat yourself." Strunk and White say, "Revise and Rewrite," where Martin Fowler says, "refactor." And the list goes on.
In fact, much of Strunk and White's advice doesn't even have to be paraphrased to apply to software. Here are a few examples, taken verbatim from the table of contents of The Elements of Style:
- Choose a Suitable Design and Hold to It
- Be Clear
- Do Not Take Shortcuts at the Cost of Clarity
Q: Which new principles in Effective Java might be integrated as best practices into the NetBeans IDE?
A: Many of the rules in the first edition have been captured as "inspections" in IDEs such as NetBeans, IntelliJ, and Eclipse. Similarly, the FindBugs tool does a good job of automating these checks. I suspect that many of the new rules will find their way into these tools.
As a simple example, I know that IDEs already check for the presence of the @Override
annotation and warn you when you've forgotten it.
How Generics Worked Out
Q: In 2003, you commented that if any of the changes in J2SE 5.0 might present particular challenges for Java developers, it would be generics, because developers would "have to get used to providing additional information in declarations." How do you think generics have worked out?
A: Merely adding type information to declarations hasn't proven to be all that difficult, but other aspects of generics have proven far more challenging. For instance, we vastly underestimated the complexity of wildcard types. They were added late in the development cycle, and we didn't appreciate all of the subtleties they introduced.
Generics certainly improved the type safety and expressiveness of the language, and I'm very glad they were added. But they haven't been an unqualified success. You only have to peruse Angelika Langer's 513-page Java Generics FAQs to appreciate this. So I wish we'd been able to simplify the design.
I also wish we'd found some way to make generics work with primitive types, so that we could have avoided autoboxing and auto-unboxing, which have proven bug prone.
What do you think this program prints? If you have any doubts, run it:
public class Unbelievable {
static Integer i;
public static void main(String[] args) {
if (i == 42)
System.out.println("Unbelievable");
}
}
The Future of the Java Platform
Q: Do you want to say anything about the future of the Java platform generally and closures in particular?
A: It's difficult to predict the future of the platform. Things have been moving a bit slowly since the release of Java SE 6, and no one is quite sure what is coming in Java SE 7, or when it's coming.
I'm convinced that the Java programming language has used up its complexity budget with the changes introduced in release 5.0. It would be a huge mistake to add any new language features that significantly increased the complexity of the language. I would place BGGA Closures squarely in this category. For more on this topic, you can take a look at the Closures Controversy talk.
A modest collection of minor language changes could improve the language, but it would have to be done with the utmost restraint.
On Being Zany and Goofy While Developing
Q: You have a long history of emphasizing fun in everything you do. At your defense of your Ph.D. dissertation, you answered a planted question with a rap song backed by a recorded rhythm track on a hidden tape recorder. And you and Neal Gafter did a "Java Puzzlers" routine in which you donned mechanics' overalls and called yourselves "Click and Hack, the Type-It Brothers," after the radio show Car Talk . What can you say about the importance of zaniness, goofiness, and joy in the process of developing?
"I'm convinced that the Java programming language has used up its complexity budget with the changes introduced in release 5.0. It would be a huge mistake to add any new language features that significantly increased the complexity of the language."
Joshua Bloch Author, Effective Java, Second Edition
A: It's a huge part of who I am and what I do. As I always say, computer science is an immature discipline, and I aim to keep it that way. I do much better work when I'm having fun and pursuing my passion. I'm deeply thankful that I've been able to spend so much of my academic and professional careers doing exactly that.
Advice for New Developers
Q: Effective Java is a book written for experienced Java developers. Do you have any advice for how a computer science major in college might benefit from the book? Or an experienced developer moving from another language to Java?
A: Many programmers find it useful to keep a copy at their desk so they can look at the code examples and such while they work. Also, I've seen people use the book's "Item" references in comments explaining their design decisions. Needless to say, I'm honored that they've done so.
As for programmers moving from another language, I think they'd do well to read a quick introduction to Java before tackling my book. Peter Sestoft's book Java Precisely would be an excellent choice.