I have been using Java since ’95 and I just reread Effective Java Second Edition by by Joshua Bloch, it is must have reference book for any Java programmer. It consists of over 75 guidelines for writing better code in Java or any other object oriented language for that better. I describe some of my favorite tips from the book:
Factories/Builders
Though, this book does not cover design patterns in general but encourages use of factories instead of constructors in certain cases. I also use this advice in cases when I have a lot of constructors and it is difficult to tell which constructor should be used. The factory methods also allows instantiating and returning subclass types. Though, the downside is that factory methods look like other static methods so it may not be obvious. I also use builders when I need to create a class with a lot of parameters and some of those parameters are optional. I often use following interface to build objects:
public interface Builder<T> { public T build(); }
Use enum for defining Singleton
As Java does not offer language level construct for defining Singletons (as opposed to Scala), defining Singleton can be difficult. The general rule I follow is to make the constructor private and define a static method for getting instance. However, there are still gotchas of preserving Singletons especially upon deserialization. Joshua recommends using enum because it automatically takes care of those edge cases.
Avoid finalize
Since, Java borrowed much of its syntax from C++, it created finalize method for a sort of desctructor. It is often misunderstood by new Java developers because it’s not a destructor. Also, it may not be called by the Java at all. Joshua recommends using try/finally block for any kind of resource allocation/release. I think closures in Java 7 may also help in this regard.
Generics
There are tons of gotchas with using generics in Java. One of my favorite tip to reduce amount of code is to define helper methods is to type inferencing, e.g.
public static <K,V> Map<K, V> newMap() { return new HashMap<K, V>(); }
One of confusing part of generics is when to use extend vs super, the book gives easy acronym PECS (producer extend consumer super) for remembering it. For example, following code shows a method that adds items to a collection:
void popall(Collection<? super E> d) { d.add(pop()); } void pushAll(Iterable<? extends E> src) { for (E e : src) push(e); }
Functional Programming
Though, Java is not a functional language, but Joshua offers some tips on creating functors or function objects, e.g.
interface UnaryFunction<T> { public T apply(T arg); }
Now this interface can be implemented for various operations and give a flavor of functional programming. Again, closures in Java 7 would help in this regard.
EnumSet and EnumMap
I have rarely used EnumSet and EnumMap in practice, but the book offers useful tips for using those instead of bit masking and manipulation.
Threading
Though, I like Java Concurrency in Practice for concurrency and threading related topics. But the book offers good tips such as using immutable classes and use of synchronization features of Java for writing thread-safe applications. One of my favorite tip is to use double check idiom, I learned that when Java initially came out and synchronization was somewhat expensive. However, I stopped using it due to some of concerns in Java memory model. Java 1.5 has fixed the Java memory model so we can use it again by declaring the shared field as volatile and doing something like:
volatile FieldHolder fh; ... void foo() { FieldHolder ref = fh; if (ref == null) { synchronized (this) { ref = fh; if (ref == null) { fh = ref = compute(); } } } }
Another tip is to use lazy init holder pattern for initializing static field lazily that requires some synchronization, e.g.
class MyClass { static class FieldHolder { static Field field = compute(); } static Field get() { return FieldHolder.field; } static synchronized Field compute() { ... } }
Other threading tips include use of Executors and classes from new Java concurrency library (java.util.concurency).
Cloning and Serialization
The short answer for cloning is not to use it and instead use constructors to copy objects. The serialization creates a lot of security problems and can be difficult when using inheritance.
Exceptions
Joshua also offers a lot of good advice for exceptions such as using exceptions with proper abstraction level. Though, he still suggests use of checked exceptions for recoverable errors but my suggestion is to use RuntimeException for both application/non-recoverable exceptions and system/recoverable exceptions.
equals, hashCode, toString, Comparable
Writing equals method when inheritance is used can also be difficult and Joshua offers a lot of good advice on writing correct equals method. Another gotcha is to make sure hashCode matches semantics of equals method. Also, it good idea to implement Comparable interface so that it’s easy to use sorted collections.
Conclusion
I briefly wrote some of my favorite tips from the book, again it’s absolute desk reference book.