Review: "Effective Java", 2nd edition

One of the benefits of working at Google that I hadn't heard about until I joined is that you're both actively plied with books when you start, and given annual credit to buy books of your choice (without the hassle of filing expenses).

Any developer who wanted a copy of the second edition of "Effective Java" was offered one when it came out, and I eventually read through it, and I finally found the time to write down a few notes...

The first thing you'll notice is that the second edition is much thicker than the first edition. The last page of actual content in the first edition was page 232; it's page 315 in the second edition. That might not seem like much of an increase, and there's a way to go before it hits the "weighty tome" territory of something like "Mac OS X Internals", but for me those extra pages take "Effective Java" over the threshold from "slim volume" to "book".

In the years from 2001 to 2008, the Java language has changed a lot. Already by page 16 (item 2), we're faced with a bounded wildcard type. Those of us who've been using Java between the two editions have also changed a lot, and a lot of the advice from the first edition, even if you weren't already following it at the time the book came out, has since become the standard idiom that everyone above a basic level of competence uses. As a result, the second edition feels very repetitive.

I really wish there were two variants: "diffs" for those of us who know the first edition and have been following its advice for years, and "from scratch" for those new to Java (or who don't read much). It's not hard to imagine "Effective Java" making a better website than a book, too. Most of the items, especially if you're familiar with the first edition, are things you can grok from the title, with little to add for the expert.

This is in stark contrast to how I remember the first edition. I remember being persuaded of several idioms by the first edition. Some things like "Return zero-length arrays, not nulls" were things we'd already worked out for ourselves, and "Minimize the scope of local variables" has been something that good programmers have understood since the beginning of time, but I remember it feeling really good to hear a voice of authority say "Avoid unnecessary use of checked exceptions". We all knew there was something wrong, but you had to have a certain stature to stand up and say "the emperor has no clothes", and the guy who was the public face of the collections classes had that stature.

The interesting thing reading the second edition is that it's likely to elicit a raised eyebrow now and again, and remind you that, presumably, there are (new) beginners who need to be told of things we've all been doing since the 1990s.

As an aside, an interesting difference between "Effective Java" and "Effective C++" is that my reaction to the latter was to come up with a set of SOPs that mainly boil down to "don't use C++ feature x". Whereas "Effective Java" was more like "use idiom x when using Java feature y". An interesting consequence of which is that I'm much better able to articulate why I do things the way I do in Java than I am in C++.

Anyway, what stood out for me in the second edition of "Effective Java"?

I didn't know about @code and @literal in JavaDoc; I tend to just write as if comments are plain text, since my API documentation isn't likely to be published anyway, and is more useful to me and others if it's readable in the source form, in contrast to Bloch's "generated documentation readability trumps source code readability". When you're the public face of the collections classes, I guess you have a different perspective.

Bloch's claim that AbstractList.removeRange is "of no interest to end users of a List implementation" made me laugh. And not in a good way. I think that specific API design error has singlehandedly caused more O(n^2) behavior in code I've been exposed to than any other, as people fail to find a bulk remove and use a "for" loop instead. Even if you do know about subList().clear(), it's hard to consider that anything but ungainly. Bulk operations seem more fundamental to me than the idea of a view, I guess, and I rarely have use for views other than because I'm trying to removeRange. Personally, I think that was an attempt to introduce an non-Java idiom into Java, and it didn't really work.

Perhaps because of Bloch's background, much of the advice in the book seems overly biased towards public API. This is understandable, but even if one takes the position that all API should be designed as if it were library API, I think it's hard to argue that you should make any more public API than absolutely necessary. YAGNI, after all, and anyone who hasn't realized that it's far easier to add stuff than to take stuff out or fix stuff, well, that person's obviously not a golfer.

Another interesting contrast with C++ is that, whenever you read a book on C++ you're likely to spend most of your time thinking about how the language could be improved, but reading books like this on Java, you're more likely to think about how the libraries could be improved. It's particularly amusing that the "Effective C++" item "Make interfaces easy to use correctly and hard to use incorrectly" (advice valid for any language, and that ties in to my earlier concern about exposing too much API) has an example "pathologically bad" Date class that's not actually as bad as the real java.util.Date.

I've seen Stroustrup actually use the real Java Date and Calendar classes as examples of bad design on a slide, and I felt it was a shame he detracted from his own point by implying that it was Java's fault. As if you can't write utter crap in any language. As if even good programmers don't have days where everything they touch turns to shite.

As if bad design (like good design) isn't universal.

Java programmers have the advantage of a large and readable library. So there's no excuse for not having seen a large amount of code, much of it high-quality, but enough of it low-quality, or old enough to be using idioms that we've since learned don't work out well. So for an experienced Java programmer, "Effective Java" is a quick read, many chapters of which could be replaced with "use common sense" or "copy the good examples, ignore the bad ones". It's sad to see the number of "obvious" points from the first edition that the JDK still falls down on. "Adhere to generally accepted naming conventions" is one that's getting worse with time as more new JDK APIs arrive using the less popular naming conventions and making them more generally accepted (using "X" and "setX" instead of "getX" and "setX", say, which isn't bad in and of itself, but is just one little bit of extra trivia we have to keep around that adds no value, and is too late to retrofit to the entire JDK). "Include failure-capture information in detail messages" is my personal unfavorite, though it's something programmers in all languages get wrong. Why aren't we teaching "how to write a good bug report" and "how to write a good error message" in the schools? Can you really be a productive member of society without these skills?

The synchronization stuff's been usefully updated in the second edition, and is a lot clearer now. It's a shame that stuff like that, which I'd go so far as saying a majority of Java programmers don't properly understand, isn't visibly treated as being any more important than all the fluff about exceptions and serialization and doc comments and (I'm not joking) the performance of string concatenation.

CopyOnWriteArrayList and its suitability for listener lists was new to me (despite dating back to Java 5). I'll have to remember that. It would be nice to have a new Java tutorial, for experienced programmers, that shows the current best idioms, and explains the rationale. Listener lists are a lot harder to get right than you might hope.

Item 69 settled an argument we'd been having at work. Should you call ConcurrentHashMap.get before calling ConcurrentHashMap.putIfAbsent? Our argument was focused on how expensive the constructor for the to-be-inserted object is. The documentation led us to believe that the implementation effectively does a get straight away, which is true, but the documentation is missing the rather important fact that a lock is taken out for the duration of the call to putIfAbsent. When Bloch presents the get-then-putIfAbsent idiom, he gives this as the motivation, and checking the source shows he's right. I've filed Sun bug 6737830 for improvements to the documentation. The evaluation for that bug is interesting, as it argues back in the opposite direction.

It's nice to see CountDownLatch advertised. That's my favorite low-level primitive.

Bloch suggests using System.nanoTime in preference to System.currentTimeMillis for interval timing, because it's more accurate, more precise, and unaffected by system clock changes. I'd been using System.nanoTime in new code, but given that last advantage maybe it's time (ho ho) for a more thorough toilet-cleaning.

The book ends with a bunch of items on serialization, which was rather surprising to me. I haven't written a (deliberately, explicitly) serializable class in the last eight years. I don't know if that's because serialization isn't a mainstream thing, or because it was eight years ago that I last wrote Java (rather than C++) for a living. Maybe just behind some narrow door in all my favorite bars, Java programmers in red Pendleton shirts are getting incredible kicks from squirting serialized objects at each other. Who knows?

Every time I've touched Java serialization I've come away with the feeling that it's sufficiently broken in a sufficient number of ways that I'm better off just avoiding it. Gangrenous appendages eventually fall off of their own accord, don't they?

If you're a Java programmer, you ought to read this book. It's not going to be a life-changing experience for many. (If you find it is, you probably need to ask the best programmer you know to help mentor you.) This isn't as important a book for Java programmers as "Effective C++" is for C++ programmers. It's also not as important a book for Java programmers as are Goetz's or Wadler's recent books.

But you can't not have read it. Get work to buy a copy and hand it round as each of you finishes. You can read through it in a day, and quicker than that if you're familiar with the first edition and you've been writing (and reading) Java in the intervening years.