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);
}

Enums and Annotations

Item 34: Use enums instead of int constants

Use enums any time you need a set of constants whose members are known at compile time.

enum classes export one instance (singleton) for each enumeration constant via a public static final field. They are compile-time type safe, can have arbitrary methods and fields and implement arbitrary interfaces.

For example,

public enum Planet {
    MERCURY(3.302e+23, 2.439e6),
    VENUS  (4.869e+24, 6.052e6),
    EARTH  (5.975e+24, 6.378e6),

    private final double mass;
    private final double radius;

    Planet(double mass, double raduis) {
        this.mass = mass;
        this.raduis = raduis;
    }
}

Item 35: Use instance fields instead of ordinals

Avoid the enum’s ordinal() method, unless writing enum-based data structures such as EnumSet and EnumMap.

Item 36: Use EnumSet instead of bit fields

The EnumSet class combines the conciseness and performance of bit field without the disadvantages of

Item 37: Use EnumMap instead of ordinal indexing

Use EnumMap (a implementation of Map with Enum as key) instead of enum’s ordinal() for indexing.

Item 38: Emulate extensible enums with interfaces

Java does not allow extending enum type. Instead, have the enum classes implementing an interface. If needed, use EnumSet and EnumMap to combine those enum classes.

Item 39: Prefer annotations to naming patterns

For example, Junit deprecated the test prefix with @Test annotation.

The naming pattern has these disadvantages over annotation:

  1. typo errors result in silent failures.
  2. no way to ensure they are used only on appropriate program elements.
  3. no good way to associate parameter values with program elements.

Item 40: Consistently use the Override annotation

The compiler can ensures a method can have @Override if and only if it is overriding a method. This avoid typo or overloading accidentally.

Item 41: Use maker interface to define types

A maker interface is an interface that contains no method declarations but merely “marks” a class that implements the interface as having some property. e.g., the Serializable interface.

In general, it is more preferable to use marker interface over marker annotation (item 39) because

  1. marker interface can catch error at compile time while marker annotation (item 39) can catch error at run time,
  2. marker interface can target precisely (instead of all java element)

Lambdas and Streams

Item 42: Prefer lambdas to anonymous classes

// this is an anonymous class
Collections.sort(words, new Comparator<String>() {
    public int compare(String s1, String s2) {
      return Interger.compare(s1.length(), s2.length());
    }
  });

// this is a lambda
Collections.sort(words,
    (s1, s2) -> Integer.compare(s1.length(), s2.length()));

Lambdas make the code more succinct. Omit the types of all lambda parameters unless their presence makes the program clearer.

However, lambdas lack names and documentation. If a computation isn’t self-explanatory, or exceeds a few lines, don’t put it in a lambda. One line is ideal for a lambda, and three lines is a reasonable maximum.

Also, lambdas is limited to functional interface. Uses anonymous classes when there are multiple methods to implement.

Item 43: Prefer method references to lambdas

// this is a lambda
map.merge(key, 1, (count, incr) -> count + incr);

// this is a method reference
map.merge(key, 1, Integer::sum);

Method references make the code even more succinct. Use them when they are available.

Item 44: Favor the use of standard functional interface

The java.util.function package provides a large collection of standard functional interfaces.

Here are the six most important functional interfaces:

Interface Function Signature Example
UnaryOperator<T> T apply(T t) String::toLowerCase
BinaryOperator<T> T apply(T t1, T t2) BigInteger::add
Predicate<T> boolean test(T t) Collection::isEmpty
Function<T, R> R apply(T t) Arrays::asList
Supplier<T> T get() Instant::now
Consumer<T> void accept(T t) System.out::println

If you have to define one, always annotate it with @FunctionalInterface. It ensures that the functional interface can’t have more than one abstract method.

Item 45: Use streams judiciously

When used appropriately, streams can make programs shorter and clearer; when used inappropriately, they can make programs difficult to read and maintain.

If you are not sure whether a task is better served by streams or iteration, try both and see.

Item 46: Prefer side-effect-free functions in stream

Do not use the streams API but not the streams paradigm!

The most important part of the streams paradigm is to structure your computation as a sequence of transformations where the result of each stage is as close as possible to a pure function of the result of the previous stage. That means any function objects passed into stream operations should be free of side-effects.

For examples, the forEach stream operation vs the for each loop.

Item 47: Prefer Collection to Stream as a return type

Collection or an appropriate subtype is generally the best return type for a public, sequence-returning method.

Item 48: Use caution when making streams parallel

Parallelizing a pipeline is unlikely to increase its performance if the source is from Stream.iterate, or the intermediate operation limit is used.

Methods

Item 49: Check parameters for validity

comments powered by Disqus