Wai Hon's Blog

Effective Java

Creating and Destroying Objects

Item 1: Consider static factory methods instead of constructors

Static factory methods are more flexible. It can choose a good name, reuse and return any object.

Item 2: Consider a builder when faced with many constructor parameters

Especially when most of the constructor parameters are optional.

Sample Code: Create an inner Builder class for constructing the object.

public class Foo {
  private final int i;  // required
  private final int j;  // required
  private final int k;  // optional

  public static class Builder {
    public Builder(int i, int j) {
      this.i = i;
      this.j = j;
    }

    public Builder k(int val) {
      k = val;
      return this;
    }

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

  private Foo(Builder builder) {
    i = builder.i;
    j = builder.j;
    k = builder.k;
  }
}

Item 3: Enforce the singleton property with a private constructor or an enum type

The lack of a public or protected constructor guarantees exactly one instance will exist once the class is initialized.

Sample Code:

public class Elvis {
  private static final Elvis INSTANCE = new Elvis();
  private Elvis() { ... }
  public static Elvis getInstance() { return INSTANCE; }

  public void leaveTheBuilding() { ... }
}

Sample Code: with Enum type: The author thinks it is more concise, provides the serialization machinery for free, and provides an ironclad guarantee against multiple instantiation. However, it is unnatural and does not work if the singleton must extend a superclass other than Enum.

public enum Elvis {
  INSTANCE;

  public void leaveTheBuilding() { ... }
}

Item 4: Enforce noninstantiability with a private constructor

A class can be made noninstantiable by including only a private constructor.

A common mistake is to enforce noninstantiability by making a class abstract. It is bad because the abstract class could be subclassed then instantiated. Also, user could wrongly assume the class was designed for inheritance.

public class UtilityClass {
  private UtilityClass() {
    // this prevent this and its subclass to be instantiated.
    throw new AssertionError("UtilityClass should not be instantiated.");
  }
  ...
}

Item 5: Prefer dependency injection to hardwiring resource

Do not have the class create resources directly. Instead, pass the dependency into the constructor.

Item 6: Avoid creating unnecessary objects

Reuse a single object instead of creating a new functionally equivalent object each time it is needed.

Examples:

Item 7: Eliminate obsolete object references

Unintentional object reference retention excludes the object from garbage collection, thus cause memory leaks.

Tips:

Item 8: Avoid finalizers and cleaners

They don’t run promptly and there is no guarantee when or whether they will be executed. They are slow to run in JVM. They silently swallow uncaught exceptions. They open up to finalizer attack.

Instead, have your class implement AutoCloseable, and require its clients to invoke the close method, using try-with-resource.

Item 9: Prefer try-with-resources to try-finally

The try-with-resources statement ensures that each resource (implementing AutoCloseable) is closed at the end of the statement.

See https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html

Methods Commons to All Objects

Item 10: Obey the general contract when overriding equals

Check these general contract when overriding equals:

  1. Reflexive: For any non-null reference value x, x.equals(x) must return true.
  2. Symmetric: For any non-null reference values x and y, x.equals(y) == y.equals(x).
  3. Transitive: For any non-null reference values x, y, z, if x.equals(y) and y.equals(z), then x.equals(z).
  4. Consistent: For any non-null reference values x and y, x.equals(y) must consistently return true or consistently return false.
  5. Non-nullity: For any non-null reference value x, x.equals(null) must return false.

equals is generally used for value classes (Integer or String), which has a notion of logical equality.

Item 11: Always override hashCode when you override equals

Equal objects mush have equal hash codes. Violation breaks collection like HashMap and HashSet.

Use the Guava’s com.google.hash.Hashing.Objects#hash to generate hash.

Item 12: Always override toString

Return a concise, useful description of the object, in an aesthetically pleasing format.

Item 13: Override clone judiciously

Object.clone() throws a CloneNotSupportedException unless it implements the interface Cloneable. The default implementation performs a shallow copy. When deep copy is needed, override judiciously.

Item 14: Consider implementing Comparable

compareTo, declared in Comparable, is similar to equals but permit order comparisons. It enables features like Arrays.sort, TreeSet, with small amount of effort.

Classes and Interfaces

Item 15: Minimize the accessibility of classes and members

Make each class or member as inaccessible as possible, for encapsulation.

Item 16: In public class, use accessor methods, not public fields

To preserve the flexibility to change the class’s internal representation.

There are always exceptions (e.g., libraries need to be best performance, like Point and Dimension in java.awt). It is less harmful if the fields are immutable.

Item 17: Minimize mutability

Immutable objects have exactly one state. They are thread-safe, require no synchronization.

The major disadvantage is that they require a separate object for each distinct value. Creating these objects can be costly.

Item 18: Favor composition over inheritance

Inheritance is problematic because it violates encapsulation. The subclass could be broken if the superclass is changed from release to release and is not designed for inheritance. Instead, use composition and forwarding.

Item 19: Design and document for inheritance or else prohibit it

The class must document precisely the effects of overriding any method. However, this is bad because it contain implementation details.

Item 20: Prefer interfaces abstract classes

Java permit only single inheritance and it restrict inheritance but not interfaces.

Starting from Java 8, interface can have default method.

Item 21: Design interfaces for posterity

For example, the default method introduced in Java 8 can fail existing implementation of the interface at runtime.

Item 22: Use interfaces only to define types

Do not use interface merely to export constants. Alternatives

  1. if the constants are strongly tied to an existing class or interface, add them there.
  2. if the constants are best viewed as members of an enumerated type, export them with an enum type. (item 34)
  3. otherwise, export the constants with a noninstantiable utility class. (item 4)

Item 23: Prefer class hierarchies to tagged classes

Tagged classes are verbose, error prone, and inefficient. They are just a pallid imitation of a class hierarchy.

class TaggedFigure {
  enum Shape { RECTANGLE, CIRCLE };

  // Tag
  final Shape shape;

  double length, width;
  double radius;

  TaggedFigure(double radius) {
    shape = CIRCLE;
    this.radius = radius;
  }

  TaggedFigure(double length, double width) {
    shape = RECTANGLE;
    this.length = length;
    this.width = width;
  }
}

Item 24: Favor static member classes over nonstatic

If a member class does not require access to an enclosing instance, put the static modifier in its declaration.

Item 25: Limit source files to a single top-level class

This rule guarantees it can’t have multiple definitions for a single class at compile time.

Generics

Terminologies:

Term Example Item
Parameterized type List<String> 26
Actual type parameter String 26
Generic type List<E> 26, 29
Formal type parameter E 26
Unbounded wildcard type List<?> 26
Raw type List 26
Bounded type parameter <E extends Number> 29
Recursive type bound <T extends Comparable<T>> 30
Bounded wildcard type List<? extends Number> 31
Generic method static <E> List<E> asList(E[] a) 30
Type token String.class 33

Item 26: Don’t use raw type

Raw type exist primarily for compatibility with pre-generics code before Java 5. Using raw types lose all the safety and expressiveness benefits of generics.

Item 27: Eliminate unchecked warnings

Eliminating all unchecked warnings assures typesafe (no ClassCastException at runtime).

If you cannot eliminate a warning, but can prove that it is typesafe, then

  1. suppress it with @SuppressWarnings("unchecked") on the smallest scope and,
  2. add a comment explain why it is safe.

Item 28: Prefer lists to arrays

Type errors happen at runtime for array but compile time for list.

Item 29: Favor generic types

They are safer and easier to use than types that require casts in client code.

Item 30: Favor generic methods

They are safer and easier to use than methods requiring explicit casts on input parameters and return values.

Item 31: Use bounded wildcards to increase API flexibility

Even Integer is a subtype of Object, Iterable<Integer> is not a subtype of Iterable<Object>.

For example,

public class Stack<E> {
    // Bad: Iterable<Integer> could not be pushed to Stack<Integer>
    void pushAllBad(Iterable<E> src);

    // Good: Iterable<Integer> can be pushed to Stack<Integer>
    void pushAllGood(Iterable<? extends E> src);
}

Item 32: Combine generics and varargs judiciously

varargs method could be unsafe due to the use of array (see Item 28: Prefer lists to arrays)

Use @SafeVarargs to annotate safe varargs method. i.e.,

  1. does not write to the varargs parameter array, and
  2. does not make the array visible to untrusted code.

Item 33: Consider typesafe heterogeneous containers

Sometimes, we need more flexibility over regular containers with fixed type parameter.

The Favorites class below is typesafe heterogeneous container.

public class Favorites {
    private Map<Class<?>, Object> favorites = new HashMap<>();

    public <T> void put(Class<T> type, T instance) {
        favorites.put(Objects.requireNonNull(type), instance);
    }
    public <T> T get(Class<T> type) {
        return type.cast(favorites.get(type));
    }
}

public static void main(String[] args) {
    Favorites f = new Favorites();
    f.put(String.class, "Java");
    f.put(Integer.class, 0xcafebabe);
}
comments powered by Disqus