Effective Java : Creating and destroying objects

Creating and Destroying Objects

Objective: This section concerns when and how to create objects, when and how to avoid creating them, how to destroy them in a timely manner, and how to manage any cleanup actions that must be done before their destruction.

Key topics:

  1. Static Factory Methods and Constructors
  2. Builders and Constructors
  3. Singleton
  4. Noninstantiability
  5. Dependency Injection
  6. Reusable Objects
  7. Obsolete Object References
  8. Try-With-Resources and Try-Finally

Estimated time: 15-30 minutes.

———————————————————————————————————————

Static Factory Methods and Constructors

You are designing a class such as Date and you want to allow a client to obtain an instance of the class, given some input such as instant. Which API would you choose to support this use case?

 Static factory method

Date d = Date.from(instant);

 Constructor

Date d = new Date(instant);

You should use static factory methods because of the following advantages, which constructors don’t:

  • They have names.
  • They are not required to create a new object each time they are invoked.
  • They can return an object of any subtype of their return type. This advantage would also provide you with great flexibility to change implementations of subtypes from release to release, without breaking backward compatibility.
  • They help decouple service provider frameworks (i.e., systems that make APIs available to clients) from implementation providers.

———————————————————————————————————————

Builders and Constructors

Of the following two ways to design a constructor with N parameters (N > 4), which option would you choose?

 Constructor

public class MyClass {
private final int param1; // required
private final int param2; // required
private final int param3; // required
private final int param4; // optional
private final int param5; // optional

public MyClass(int p1, int p2, int p3) {
this(p1, p2, p3, 0);
}

public MyClass(int p1, int p2, int p3, int p4) {
this(p1, p2, p3, p4, 0);
}

public MyClass(int p1, int p2, int p3, int p4, int p5) {
param1 = p1; param2 = p2; param3 = p3; param4 = p4; param5 = p5;
}
}

 Builder

public class MyClass {
private final int param1;
private final int param2;
private final int param3;
private final int param4;
private final int param5;

public static class Builder {
private final int param1; // required
private final int param2; // required
private final int param3; // required
private int param4; // optional
private int param5; // optional

public Builder(int p1, int p2, int p3) {
param1 = p1; param2 = p2; param3 = p3;
}

public Builder withParam4(int p4) {
param4 = p4; return this;
}

public Builder withParam5(int p5) {
param5 = p5; return this;
}

public MyClass build() {
return new MyClass(this);
}
}

private MyClass(Builder b) {
param1 = b.param1; param2 = b.param2; param3 = b.param3;
param4 = b.param4; param5 = b.param5;
}
}

You should use builder because it makes client code much easier to read and write.

——————————————————————————————————————–

Singleton

Which of the following implementations would you choose?

 Use of enum

public enum MySingleton {
INSTANCE;

public void getDataByMarketplaceId(MarketplaceId id) { … }
}

 Use of static factory

public class MySingleton {
private static final MySingleton INSTANCE = new MySingleton();
private MySingleton() { … }
public static MySingleton getInstance() { return INSTANCE; }

public void getDataByMarketplaceId(MarketplaceId id) { … }
}

 Use of public final field

public class MySingleton {
public static final MySingleton INSTANCE = new MySingleton();
private MySingleton() { … }

public void getDataByMarketplaceId(MarketplaceId id) { … }
}

The Enum approach, though a bit unnatural, would be desirable because it is more concise and comes with free serialization machinery and provides ironclad guarantee against multiple instantiation. You can’t, however, use this approach if your singleton must extend a super class other than Enum; in this case it would be appropriate to use the static factory approach.

——————————————————————————————————————–

Noninstantiability

Which of the following implementations would you choose for a class that must be not instantiable?

 Without a private constructor

public class UtilityClass {
public static String removeEndIfPatternMatched(String target, Pattern p) { … }
}

 With a private constructor

public class UtilityClass {
private UtilityClass() {
throw new AssertionError();
}

public static String removeEndIfPatternMatched(String target, Pattern p) { … }
}

With a private constructor the class is inaccessible outside the class and cannot be subclassed; it is also inaccessible inside the class if an assertion error is added.

——————————————————————————————————————–

Dependency Injection

Which of the following implementations would you choose?

 Use of static utility

public class SpellChecker {
private static final Dictionary dictionary = new Dictionary();
private SpellChecker() {}

public static boolean isValid(String word) { … }
}

 Use of dependency injection

public class SpellChecker {
private final Dictionary dictionary;

public SpellChecker(Dictionary d) { this.dictionary = Objects.requireNonNull(d); }

public boolean isValid(String word) { … }
}

 Use of singleton

public class SpellChecker {
private final Dictionary dictionary = new Dictionary();
private static final SpellChecker INSTANCE = new SpellChecker();
private SpellChecker() { … }

public static SpellChecker getInstance() { return INSTANCE; }

public boolean isValid(String word) { … }
}

Static utility and singleton are not relevant for classes whose behavior is parameterized by another resource; use of dependency injection instead would greatly improve flexibility and testability (e.g., you can change/use different implementations of  Dictionary  for different languages easily at any time).

——————————————————————————————————————–

Reusable Objects

Which of the following implementations would be desirable?

 Use of String.matches

public class RomanNumber {
static boolean isRomanNumber(String s) {
return s.matches(“^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$”);
}
}

 Use of Pattern.matcher

public class RomanNumber {
private static Pattern ROMAN = Pattern.compile(“^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$”);

static boolean isRomanNumber(String s) {
return ROMAN.matcher(s).matches();
}
}

While  String.matches  is the easiest way to check if a string matches a regular expression, you should reuse expensive objects such as  Pattern in performance-critical situations.

——————————————————————————————————————–

Obsolete Object References

Can you spot a memory leak problem in one of the following two implementations?

 Stack implementation 1

public class Stack {
private Object[] elements;
private int size = 0;
private static int final INITIAL_CAPACITY = 8;

public Stack{} { elements = new Object[INITIAL_CAPACITY]; }

public void push(Object o) { … }

public Object pop() {
if (size == 0)
throw new EmptyStackException();
return elements[–size];
}
}

 Stack implementation 2

public class Stack {
private Object[] elements;
private int size = 0;
private static int final INITIAL_CAPACITY = 8;

public Stack{} { elements = new Object[INITIAL_CAPACITY]; }

public void push(Object o) { … }

public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object o = elements[–size];
elements[size] = null;
return o;
}
}

You should use the second implementation because it eliminates obsolete references. Although the first implementation can pass all the tests you could have, it causes a memory leak because the object referred by  elements[size]  is excluded from garbage collection; such memory leaks, in extreme cases, can cause serious problems with  OutOfMemoryError . Other common cases that may cause memory leak problems are implementations of caches, listeners, and callbacks.

——————————————————————————————————————–

Try-With-Resources and Try-Finally

Which of the following two implementations would you prefer?

 Use of try-finally

static void copyFile(String src, String dst) throws IOException {
InputStream in = new FileInputStream(src);
try {
OutputStream out = new FileOutputStream(dst);
try {
byte[] buffer = new byte[BUFFER_SIZE];
int n;
while ((n = in.read(buffer)) >= 0)
out.write(buffer, 0, n);
} finally {
out.close();
}
} finally {
in.close();
}
}

 Use of try-with-resources

static void copyFile(String src, String dst) throws IOException {
try (InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dst)) {
byte[] buffer = new byte[BUFFER_SIZE];
int n;
while ((n = in.read(buffer)) >= 0)
out.write(buffer, 0, n);
}
}

You should use try-with-resources versions because they are the best way to close resources. In addition, the code is shorter, more readable and concise, and provides far better diagnostics.

Leave a Reply

%d bloggers like this: