Tech Tenets

Tech Tenets

Creating and Destroying Objects

Subscribe to my newsletter and never miss my upcoming articles

Part of a Series

The Index

  • 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

Item 1: Consider static factory methods instead of constructors

Advantages:

  1. Have names (multiple constructors with variable args)
  2. Not required to create a new object each time they’re invoked (caching)
  3. Can return an object of any subtype of their return type (flexibility, conceptual light, ex Java Collections)
  4. Class of the returned object can vary from call to call as a function of the input parameters (ex EnumSet)
  5. Class of the returned object need not exist when the class containing the method is written

Some limitations of the same:

  1. Classes without public or protected constructors cannot be sub-classed (call to super()?)
  2. 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:

  1. as a safety net in case client forgets to close() (performance cost)
  2. 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:

  1. easier to read even for multiple resources
  2. auto closing (AutoCloseable)
  3. 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!

#java#programming#best-practices
 
Share this
Proudly part of