Table of contents
- Item 1: Consider static factory methods instead of constructors
- Item 2: Consider a builder when faced with many constructor parameters
- Item 3: Enforce the singleton property with a private constructor or an enum type
- Item 4: Enforce non-instantiability with a private constructor
- Item 5: Prefer dependency injection to hardwiring resources
- Item 6: Avoid creating unnecessary objects
- Item 7: Eliminate obsolete object references
- Item 8: Avoid finalizers and cleaners
- Item 9: Prefer try-with-resources to try-finally
Part of a Series
Item 1: Consider static factory methods instead of constructors
Advantages:
- Have names (multiple constructors with variable args)
- Not required to create a new object each time they’re invoked (caching)
- Can return an object of any subtype of their return type (flexibility, conceptual light, ex Java
Collections
) - Class of the returned object can vary from call to call as a function of the input parameters (ex
EnumSet
) - Class of the returned object need not exist when the class containing the method is written
Some limitations of the same:
- Classes without
public
orprotected
constructors cannot be sub-classed (call tosuper()
?) - Hard for programmers to find (visibility in Javadoc)
Common names of static factory methods: from, of , valueOf, getInstance, newInstance, getType, newType.
Understand relative merits before deciding.
Item 2: Consider a builder when faced with many constructor parameters
Static factories and constructors don’t scale with a large number of optional parameters. They use a telescoping constructor involving multiple constructors which is hard for client to use and read.
Another choice is the JavaBean pattern in which an object is created and multiple setters called as required. The problem is a JavaBean may be in an inconsistent state partway through its construction. Another being it makes impossible for making class immutable and the client has to handle thread-safety.
The Builder (Builder design pattern) simulates named optional parameters and is well suited to class hierarchies. It is easy to read as well.
Thus, Builder pattern is a good choice when designing classes whose constructors or static
factories would have more than a handful of parameters, especially if many are optional or identical types.
Item 3: Enforce the singleton property with a private constructor or an enum type
Singleton classes are usually written in 2 ways:
public final
field
It works but special measure has to be taken in the private constructor to safeguard against reflection.
public static
factory method
It is simpler, we can change in the future if we don’t want Singleton nature and can help in a generic singleton factory. The method reference can be used as a supplier.
We can use the approach keeping in mind the merits, the former is preferred as per the author.
In either of the above approaches, the Serializable concept is not simple as we have to stop new objects while deserializing.
But, a single-element enum
type is often the best way to implement a singleton. However, enum
can’t extend a superclass other than enum
.
Item 4: Enforce non-instantiability with a private constructor
We write utility classes with public static
methods for certain helper use cases (like java.lang.Math) which are not meant to be initialized.
Making it abstract
(subclass can instantiate) or not writing any constructor (compiler provides default) won’t help.
The best way is to make the constructor private
. It won’t allow subclassing though.
Another option is to use interface
(Java 8 and above).
My 2 cents: What if we make the class final
?
Item 5: Prefer dependency injection to hardwiring resources
Static utility classes and singletons are inappropriate for classes whose behaviour is parameterized by an underlying resource.
Well, this is an easy one we can use Autowired
in Spring Boot (dependency injection framework) to make things manageable.
Item 6: Avoid creating unnecessary objects
Never use String::new
(each time creates new object) instead use static
factory methods or just string with quotes.
Cache repeatedly usable expensive objects. It is important to also find out where they are being created. String.matches()
is easy but creates Pattern
instance each time and should be avoided for high performance.
keySet
of Map
interface gives a reference of Set
view of keys each time (backed by the same Map), thus efficient.
Autoboxing blurs but does not erase the distinction between primitive and boxed primitive types. It leads to the implicit creation of objects and wastage of space and time. Thus, prefer primitives to boxed primitives, and watch out for unintentional autoboxing.
Item 7: Eliminate obsolete object references
An obsolete reference is simply a reference that will never be dereferenced again (for example: self implemented stack with an array and top reference, push some elements, top moves to right and while popping only decrement top reference, then the popped elements are on right and not required but still they are referenced by array index thus excluded from GC).
The memory leak due to them is insidious as they hoard memory silently and also avoid the objects being referenced by them to be garbage collected.
Solution: make the reference null
after using. (In above stack example make the top index null before/after popping for GC). But it should not be overdone. The best way is to define variables in narrowest scope so they fall out of scope implicitly after use.
Whenever a class manages its own memory the developer should be alert to avoid memory leaks. Another case having high chances of this are Caches and Listeners with callbacks. Use heap profiler to debug.
Item 8: Avoid finalizers and cleaners
Finalizers are unpredictable, often dangerous, and generally unnecessary. Cleaners (Java 9 and above) are less dangerous than finalizers, but still unpredictable, slow, and generally unnecessary.
There is no guarantee that they will be executed promptly, can take arbitrary long time between object become unreachable and finalizers/cleaners are run (may lead to OutofMemory Error). Behaviour is a function of garbage collector and dependent of JVM. Avoid for time-critical use cases.
Cleaners are a bit better as they run under the control of class author, but still in background under the control of garbage collector thus unreliable.
Program may finish without running them, they should be avoided for updating persistent state.
Another problem with finalizers is that an uncaught exception thrown during finalization is ignored, and finalization of that object terminates, leaving object in a corrupt state. It won’t even print a warning.
Finalizers have a security problem finalizer attacks : If an exception is thrown in constructor or serialization processs the subclass may use the half-baked object. If it is stored in a final
field, thus excluded by GC, then it is just waiting for any method to use it and it would crash. Throwing an exception from a constructor should be sufficient to prevent an object from coming into existence; in the presence of finalizers, it is not. To avoid: make a class final
or to protect non-final classes write a final
finalize()
method that does nothing.
To handle termination of resources implement AutoCloseable
and make clients call close()
after use. The resource instance should store whether its open or closed in a field and throw exception in invalid case.
Finalizers and cleaners are good to use for:
- as a safety net in case client forgets to
close()
(performance cost) - native peers because not tracked by gc, use if performance acceptable and no critical resource held by peer else use
AutoCloseable
Item 9: Prefer try-with-resources to try-finally
Historically, try-finally was the best way but no longer.
In fact, two-thirds of the uses of the close method in the Java libraries were wrong in 2007
It quickly becomes messy with a couple of resources.
If the exception is thrown in try block and then in final also, the latter one destroys the former one making debugging difficult. It can be handled with extra code but not done usually (too verbose).
Prefer try-with which has a requirement that the resource must implement AutoCloseable.
try-with benefits over try-finally:
- easier to read even for multiple resources
- auto closing (AutoCloseable)
- good exception handling, gets printed on logs, if suppression then the former is kept, can be accessed via
getSuppressed()
. Multiple exceptions can be suppressed as required and printed on logs depicting they are suppressed
This is it!