Creating and Destroying Objects
Part of a Series
- Item 1: Consider
staticfactory methods instead of constructors
- Item 2: Consider a builder when faced with many constructor parameters
- Item 3: Enforce the singleton property with a
privateconstructor or an
- Item 4: Enforce non-instantiability with a
- 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
Item 1: Consider static factory methods instead of constructors
- 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
- Class of the returned object can vary from call to call as a function of the input parameters (ex
- Class of the returned object need not exist when the class containing the method is written
Some limitations of the same:
- Classes without
protectedconstructors cannot be sub-classed (call to
- 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:
It works but special measure has to be taken in the private constructor to safeguard against reflection.
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
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.
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
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
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.
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
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
- native peers because not tracked by gc, use if performance acceptable and no critical resource held by peer else use
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!