Target Audience: This article is for a Java techie who knows the basics of Java language and looking for best practices while using the language. This article throws light on the key points from the book “Effective Java” by Joshua Bloch.
- Consider a “builder pattern” when there are too many parameters to be passed to the constructor. The builder pattern also gives the option of naming the parameters and making it optional. In case of builder pattern, while building the object itself, the inconsistencies in the state of the object can be identified.
- A “singleton pattern” with a private constructor and static factory is not thread-safe ( unless all the conditions are taken care explicitly by synchronizing ). Serialization breaks singleton and the solution is to make the variables transient. Reflection can also create multiple instances of the singleton class. The best way of writing a singleton class is using Enum.
- A Class need not be always instantiated, it can be a kind of utility class which has only static methods. In case of utility classes, the constructor should be made private. Using abstract keyword to make a class non-instantiable is a bad practice. The Abstract keyword should be used only if the class is meant for sub-classing.
- Do not create unnecessary objects. Use primitive types whenever possible, because auto-boxing creates a lot of objects. Moreover, since the wrapper types are immutable, the application can end up creating a lot of objects. Maintain your own “object pool” only if the object is really heavy weight like DB Connections, otherwise the modern GCs are efficient enough to handle lightweight objects.
- Do not write “Constant Interfaces”. Constant Interfaces are the interfaces with no behaviors and only constant values. Use a class with the private constructor to hold the list of constants.
- The Liskov Substitution Principle says that any important property of a type should also hold true for its subtypes so that any method written for the type should work equally well with its sub-types.
- A good hash function must distribute a collection of unequal instances uniformly across all possible hash values. In case of immutable objects, the hash code can be cached to improve the performance.
- Eliminate obsolete references. If a program manages its own memory, it is necessary that the object references are marked to null for the object to be garbage collected. Always keep the scope of the object very narrow. The best possible way to implement a cache is to use WeakHashMap.
- Do not override equals when each instance is always unique or there is no logical equality required or the super-classe’s “equals” method is sufficient. While overriding equals
- Always use @Override annotation
- Always check if the object to be compared, is the instance of this class
- Always compare the fields that are most likely to differ for better performance
- ALWAYS OVERRIDE HASH-CODE
- Avoid finalizers. There is no guarantee on when finalizers will execute. Never rely on the finalizers to release non-memory resources. The finalizer thread might be running at a lower priority than another application thread, so objects might not get finalized at the rate they become eligible for finalization. If an uncaught exception is thrown in finalization, then it is ignored and the finalization terminates. The finalizer chaining is not automatic and hence the super finalizer needs to be called explicitly. The best use case for finalizers are
- “Safety net” in case the explicit termination/finalization is not called
- Release the native resource.
Note: This article might not be complete in itself. This might be a trigger for the reader to get into the details of writing a good code.