“Effective Java” by Josh Bloch was one of the best books on Java programming when it was released back in 2001. It gave a set of best practices for writing Java well, from the lead architect for the JDK libraries. I found it very useful in improving my Java coding and to help me think about code from the perspective of producing re-usable libraries as well as simple solutions.
So, I was really happy to get a copy of Effective Java 2nd edition at the weekend, brought up to date with Java 1.6. As before, it provides good idioms for writing Java code, now including information on generics and concurrency, as well as updating some of the practices from the first edition. The book is excellent. However much I think I know about Java, Josh Bloch still has things to teach me. Anyone working in the Java ecosystem, whether programming Java directly or Jython, JRuby, Rhino or Groovy, should read the book to learn more about the best ways of working with Java and the Java libraries.
Mostly for my reference, I’ve pulled out below the facts from the book that were new to me.
- You can enforce the singleton property of a class by using an enum type for it - this gets rid of the need to write a custom readResolve method to preserve the singleton nature when used with serialization.
- If you have to use a finalizer, explicitly call super.finalize; and consider an anonymous nested finalizer guardian if clients may fail to do so. (I think I’ve used finalizers perhaps once or twice in more than ten years of programming Java, so this isn’t likely to be that useful to me…)
- In an equals implementation, compare floats and doubles with Float.compare and Double.compare to cope with NaN and -0.0, rather than a simple ==.
- Consider using static factories to create immutable objects as an alternative to making the whole class final - it allows extension within the package.
- “PECS” is a useful acronym to remember how to use generics wildcards - producer extends, consumer super.
- Consider using a nested enum to provide a strategy pattern to share implementation between several instances of the outer enum (although in many cases, this seems like it could create more code than it saves).
- Use EnumMap for maps keyed by an enum.
- In a constructor for an immutable object, make defensive copies of passed-in mutable objects before checking for validity to avoid possible synchronization issues if those objects are changed in-between the check and the defensive copy.
- Use Arrays.toString to replace use of Arrays.asList for printing out an array.
- Use @code and @literal in JavaDoc to replace <code> and escaping of entities.
- Use a CopyOnWriteArrayList inside an observable class to hold observers - to remove the need for synchronization. (note: what about making this use weak references too? is there a library class for this?)
- Extendable serializable classes with instance fields that should not be initialized to their default values should implement the readObjectNoData method and throw an InvalidObjectException.
- Consider using a serialization proxy, with readResolve and writeReplace referencing the proxy, to avoid the complexity of writing good serialization code for complex objects.