Effective Java

This blog contains notes on Joshua Bloch’s Effective Java. I may also include some Java 8 specific entries after that. There are following parts:

  • Creating and Destroying Objects
  • Methods Common to All Objects
  • Classes and Interfaces
  • Generics
  • Enums and Annotations
  • Methods
  • General Programming
  • Exceptions
  • Concurrency
  • Serialization

Creating and Destroying Objects

Item 1: Static Factory Methods instead of Constructors

Advantages of static factory methods include:

  • Unlike constructors, 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.
  • They reduce the verbosity of creating parameterized type instances.
    1
    Map<String, Integer> m = HashMap.newInstance();
    Disadvantages include:
  • Classes without public or protected constructors cannot be subclassed
  • They are not readily distinguishable from other static methods

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

There are several alternatives:
(1) Telescoping constructor. This method rerquires redundant codes.
(2) JavaBeans pattern: use tons of setters. Their thread safety should be taken care of from programmers’ side.
(3) Builder pattern: can enforce checkings in build().

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

Several ways to enforce singleton property exists.

  • An obvious approach is public static final field instance.
  • The commonly mentioned one is “private static instance + private constructor + public getInstance() method”.
  • Enum singleton: a single-element enum type.

Item 4: Enforce non-instantiability with a private constructor

Otherwise the compiler might give the class a default public constructor.

Item 5: Avoid creating unnecessary objects

  • An immutable object can always be reused, like string literals.
  • Avoid creating boxing typed variables if primitive typed ones are available.

Item 6: Eliminate obsolete object references

Manually set obsolete pointers to null, so that GC can collect the spaces. Otherwise there may be memory leak.

Item 7: Avoid finalizers

  • Finalizers are not guaranteed to run soon, so never do anything time-critical in finalizers.
  • If we want an explicit termination method, use it with try-finally construct, where the finally block is guaranteed to execute in a timely manner.

Methods Common to All Objects

Item 8: Obey the general contract when overriding equals

There are five requirements in the general contract:

  • Reflexivity: anything.equals(itself)
  • Symmetry: if a.equals(b) then b.equals(a))
    An important point is the case of “is-a”. There is no way to extend an instantiable class and add a value component while preserving the equals contract (unless OOP abtraction is broken)
  • Transitivity: If a==b and b==c, then a==c
  • Consistency: If a==b, then a==b always true (unless a or b are modified)
  • Non-nullity: null != anything

Item 9: Always override hashCode when you override equals

A hashCode() method should ensure that equal objects receive equal hash codes, while avoid hash collisions.

Item 10: Always override toString

And make the contents humanly readible.

Item 11: Override clone judiciously

  • If you override clone in a nonfinal class, you should return an object obtained by invoking super.clone().
  • Never make the client do anything the library can do for the client.
  • In effect, the clone method functions as another constructor.
  • Consider using a copy constructor instead.

Item 12: Consider implementing Comparable

  • Throw ClassCastException if two object references being compared to refer to objects of different classes.

Classes and Interfaces

Item 13: Minimize accessibility of classes and members

  • For top-level classes and interfaces, only package-private or public are possible.
  • For members (fields, methods, nested classes, nested interfaces) there are four levels of accessibility: private, package-private (default; no modifier), protected, and public.
  • Instance fields should never be pubic. Otherwise it is at least not thread-safe. Use accessor methods (getter, setter) instead. (Item 14)

Item 14: In public classes, use accessor methods, not public fields

Item 15: Minimize mutability

To make a class immutable, follow these following five rules:

  1. Don’t provide mutators (methods modifying the class’s state)
  2. Ensure the class cannot be extended.
    • Either making this class final, or
    • Making all its constructors private or package-private.
  3. Make all fields final.
  4. Make all fields private.
  5. Ensure exclusive access to any mutable components.

Item 16: Favor composition over inheritance

TODO

Item 17: Design and document for inheritance, or else prohibit it

Item 18: Prefer interfaces to abstract classes

Itme 19: Use interfaces only to represent types

Because each class can implement so many interfaces, there will be tons of classes to change, if you want to modify an interface.

Item 20: Prefer class hierarchies to tagged classes

Item 21: Use function objects to represent strategies

Item 22: Favor static member classes over nonstatic

Generics

A table for terms used in the Generics chapter:

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

Item 23: Don’t use raw types in new code

In coding with legacy codes, the migration compatibility requirement drives the support of raw types.
However, there are two literals: (1) Must use raw types in class literals. (2) instanceof operator with generic type.

Item 24: Eliminate unchecked warnings

If you are very sure, suppress the warning with @SuppressWarnings("unchecked").

Item 25: Prefer lists to arrays

Arrays differ from generic types: (1) Arrays are covariant. If Sub is a subtype of Super, then Sub[] is a subtype of Super[]. Lists throw exceptions in compile time (invariant), which gives a better warning. (2) Arrays are reified. Arrays know and enforce their element types ar runtime. Lists enforce type constraints at compile time (erased).
Arrays and lists do not mix well. A generic array like List<E>[] does not compile.

Item 26: Favor generic types

Instead of using a lot of conversions on Object.
Note that a generic typed array (a non-reifiable type) triggers an exception. Two alternatives to circumvent this problem: (1) Create an array of Objects and cast them: elements = (E[]) new Object[SIZE]. (2) Make it an array of Object, and cast them when needed. Both will turn the error into a warning; suppress it.

Item 27: Favor generic methods

The type parameter list, which declares the type parameter, goes between the method’s modifiers and its return type.

1
2
3
4
// raw types - unacceptable (item 23)  
public static Set union(...) {}
// Generic method - a simple example
public static <E> Set<E> union(...) {}

One noteworthy feature of generic methods is that you needn’t specify the value of the type parameter explicitly (as you must when invoking generic constructors) — the compiler figures out. This is type inference.

  • The generic static factory pattern makes you only need to specify the class types once.
  • The generic singleton factory pattern enables you to create an object that is immutable but applicable to many different types.

Item 28: Use bounded wildcards to increase API flexibility

Producer-extends, consumer-super.

Item 29: Consider typesafe heterogeneous containers

Enums and Annotations

Item 30: Use Enums instead of int constants

Benefits of enum type:

  • Compile-time type-safe.
  • Each type has its name space.
  • Enables adding fields and methods (the fields have to be final).
  • Example of class Operation using enum: Operation.java.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public enum Operation{
    // Instance fields are String symbol in this case
    PLUS("+") { double apply (double x, double y) { return x + y; } },
    MINUS("-") { double apply (double x, double y) { return x - y; } },
    TIMES("*") { double apply (double x, double y) { return x * y; } },
    DIVIDE("/") { double apply (double x, double y) { return x / y; } };

    // Add some methods below
    private final String symbol;
    Operation(String symbol) { this.symbol = symbol; }
    @Override public String toString() {return symbol; }
    abstract double apply (double x, double y);

    public static void main(String[] args) {
    double x = Double.parseDouble(args[0]);
    double y = Double.parseDouble(args[1]);
    for (Operation op : Operation.values())
    System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
    }
    }

Item 31: Use instance fields instead of ordinals

All enums have an ordinal() method, but relying on it to derive a value is unreliable, since ordinal() value may change once you add items in the enum type.

Item 32: Use EnumSet instead of bit fields

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Bit field enumeration constants - Don't use it!
public class Text {
public static final int STYLE_BOLD = 1 << 0;
public static final int STYLE_ITALIC = 1 << 1;
public static final int STYLE_UNDERLINE = 1 << 2;
public static final int STYLE_STRIKETHROUGH = 1 << 3;
public void applyStyles(int styles) {...}
}
text.applyStyles(STYLE_BOLD | STYLE_ITALIC);

// EnumSet defining and usage:
public class Text {
public enum Style {BOLD, ITALIC, UNDERLINE, STRIKETHROUGH}
public void applyStyles(Set<Style> styles) {...}
}
text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));

Item 33: Use EnumMap instead of ordinal indexing

An EnumMap is a Map with a few restrictions:

  • It is used for mapping from an enum type (to whatever)
  • It is instantiated with the enum type: Type.class
    There are some features:
  • EnumMap provides an iterator, and performs faster than HashMap.
  • To iterate over an EnumMap, use something like for (Type t : Type.values())

Item 34: Emulate extensible enums with interfces

While you cannot write an extensible enum type, you can emulate it by writing an interface to go with a basic enum type that implements the interface.
This is widely used in operation codes. For example: the codes in item 30 can be changed to extensible.

1
2
3
4
5
6
7
8
9
10
11
public interface Operation { double apply (double x, double y); }
public enum BasicOperation implements Operation {
// Remaining thing goes here
}
public enum ExtendedOperation implements Operation {
EXP("^") { public double apply (double x, double y) {return Math.pow(x,y); } },
REMAINDER("%") {public double apply (double x, double y) {return x % y;}};
private final String symbol;
ExtendedOperation(String symbol) {this.symbol = symbol;}
@Override public String toString() {return symbol;}
}

Item 35: Prefer annotations to naming patterns

Instead of naming the jUnit tests according to nomenclatures, give it a @Test label.

Item 36: Consistently use the Override notation

… on the methods you believe to override a superclass declaration — and then the compiler can point out if the signature is different.

Item 37: Use marker interfaces to define types

A marker interface is an interface that contains no methods declarations. The difference between marker interfaces and marker annotations is that: marker interfaces are generally used to define classes and interfaces, whereas marker annotations can be used to programs.

Methods

Item 38: Check parameters for validity

Think about some restrictions on the parameters. If there are, write:

  • The assert commands.
  • Remember to write @param, @return, and @throws annotations in the Javadoc.

Item 39: Make defensive copies when needed

  • In Java, everything is passed via reference.
  • In the constructor, make a defensive copy (DON’T use clone; use copy constructors instead), to prevent the class internal being modified.
  • The defensive copies shall be made prior to validity check. Otherwise there may be TOCTOU attack (modification between Time-Of-Check and Time-Of-Use).
  • Modify accessors to return defensive copies of mutable internal fields.
  • In summary, if a class has mutable components that it get from or returns to its clients, the class must defensively copy these components.

Item 40: Design method signatures carefully

  • Choose method names carefully.
  • Don’t go overboard in providing convenience methods.
  • Avoid long parameter lists.
  • For parameter types, favor interfaces over classes.
  • Prefer two-element enum types to boolean parameters.

Item 41: Use overloading judiciously

  • The choice of which overloading to invoke is made at compile time.
  • A safe, conservative policy is never to export two overloadings with the same number of parameters.

Item 42: Use varargs judiciously

  • Variable-arity methods have problem: the behavior of passing no arguments is undefined.
  • Don’t retrofit every method that has a final array parameter; use varargs only when a call really operates on a variable-length sequence of values.
  • Arrays.asList() might even parse it to a single-item array, which is undesirable.

Item 43: Return empty arrays or collections, not nulls

An empty array or collection has 0 size, and does not throw NPE.

Item 44: Document every exposed API

… which happen to be detested by many programmers LOL.

General Programming

Item 45: Minimize the scope of local variables

Recommended: declare the variables at the first place it is used.

Item 46: Prefer for-each loops to traditional for-loops

Except when you want to modify the loop elements themselves. If you want to use the traditional for loop (using i, j, etc. as iteration pointers), be careful about how many times some objects are iterated — especially when you call something like next().

Item 47: Know and use libraries

For example, new Random() .nextInt() is evenly distributed along (0, Integer.MAX_VALUE]

Item 48: Avoid float and double if exact answers are required

Use BigDecimal, int, or long instead.

Item 49: Prefer primitive types to boxed primitives

Boxed primitives slows the calculation down by a large factor. Also, applying the == operator to boxed primitives is almost always wrong. Override and use equals() instead.

Item 50: Avoid strings where other types are more appropriate

So… avoid it whenever other types are applicable.

Item 51: Beware the performance of string concatenation

String concatenation on n strings require time quadratic to n. Since they are immutable, each contatenation constructs a new string from scratch.

Item 52: Refer to objects by their interfaces

If appropriate interface types exist, then parameters, return values, variables, and fields should all be declared using interface types. For example:

1
2
3
4
// Good
List<Subscriber> subscribers = new Vector<Subscriber>();
// Bad
Vector<Subscriber> subscribers = new Vector<Subscriber>();

This makes the code flexible.

Item 53: Prefer interfaces to reflection

Reflection allows one class to use another, even if the latter class did not exist when the former was compiled. This comes with a lot of prices. Avoid it.

Item 54: Use native (JNI) methods judiciously

Native methods can perform arbitrary computation in native languages before returning to Java. However, in many cases, JVM implementations are just much faster.

Item 55: Optimize judiciously

  • Strive to write good programs rather than fast ones.
  • Strive to avoid design decisions that limit performance.
  • Consider the performance consequences of your API design decisions.
  • Measure performances before and after each time you want to do optimization.

Item 56: Adhere to generally accepted naming conventions

// I think obeying the conventions used by the company’s large codebase is pretty practical. Other (more experienced) people know about the naming conventions.

Chapter 9: Exceptions

Item 57: Use exceptions only for exceptional conditions

  • Putting code inside a try-catch block inhibits certain optimizations done by JVM.
  • Exceptions are, as their name implies, to be used only for exceptional conditions: they should never be used for ordinary control flow.
  • A well-designed API must not force its clients to use exceptions for ordinary control flow.

Item 58: Use checked exceptions for recoverable conditions and runtime exceptions for programming errors

Java provides three kinds of throwables: checked (at compile time) exceptions, runtime exceptions, and errors. The author suggests the usages as mentioned in the title. A few other suggestions:

  • It’s best not to implement any new Error subclasses.
  • All of the unchecked throwables you implement should subclass RuntimeException.

Item 59: Avoid unnecessary use of checked exceptions

One technique for turning a checked exception into an unchecked exception is to break the method into two methods — the first one returns a boolean indicating whether the exception would be thrown.

Item 60: Favor the use of standard exceptions

  • Using standard exceptions makes your API easier to learn, use and read.
  • The most commonly reused exceptions include:
Exception Occasion for Use
IllegalArgumentException Non-null parameter value is inappropriate
IllegalStateException Object state is inappropriate for method invocation
NullPointerException Parameter value is null where prohibited
IndexOutOfBoundsException Index parameter value is out of range
ConcurrentModificationException Concurrent modification of an object has been detected where it is prohibited
UnsupportedOperationException Object does not support method

Item 61: Throw exceptions appropriate to the abstraction

Higher layers should catch lower-level excptions andm in their place, throw exceptions that can be explained in terms of the higher-level abstraction. (exception translation)
While exception translation is superior to mindless propagation of exceptions from lower layers, it should not be overused.

Item 62: Document all exceptions thrown by each method

Always declare checked exceptions indicidually, and document precisely the conditions under which each one is thrown using the Javadoc @throws tag.
Use the Javadoc @throws tag to document each unchecked exception that a method can throw, but do not use the throws keyword to include unchecked exceptions in the method declaration.

Item 63: Include failure-capture information in detail messages

printStackTrace() returns result of the exception’s toString() method. This is usually the only message the user (programmers) of your API sees when a crash happens.

Item 64: Strive for failure atomicity

Generally speaking, a failed method invocation should leave the object in the state that it was in prior to the invocation. Several methods to achieve it include:

  • Design immutable objects.
  • Order the computation so that any part that may fail takes place before any part that modifies the object.
  • Write recovery code that intercepts a failure that occurs in the midst of an operation and causes the object to roll back its initial state.
  • Perform the operation on a temporary copy of the object.

Item 65: Don’t ignore exceptions

An empty catch block defeats the purpose of exceptions. At least a comment explaining why it is appropriate to ignore the exception.

Concurrency

Item 66: Synchronize access to shared mutable data

The synchronized keyword ensures that only a single thread can execute a method or block at one time.

  • JLS guarantees that reading or writing a variable is atomic unless the variable is of type long or double.
  • Synchronization is required for reliable communication between threads as well as for mutual exclusion.
  • Do not use Thread.stop. A recommended way to stop one thread from another is to poll a boolean which is initially false, and which is set to true by another thread. However, be careful about the hoisting mechanism — use an explicit method to poll, to prevent JVM to transform into a while(true) sentence.
  • Synchronization has no effect unless both read and write operations are synchronized.
  • In summary, when multiple threads share mutable data, each thread that reads or writes the data must perform synchronization.

Item 67: Avoid excessive synchronization

  • Never cede control to the client within a synchronized mthod or block. In other words, do not invoke a method that is designed to be overridden inside a synchronized region.
  • TODO - don’t quite understand the exmaple yet.

Item 68: Prefer executors and tasks to threads

Oracle’s tutorial here.

Item 69: Prefer concurrency utilities to wait and notify

  • Given the difficulty of using wait and notify correctly, you should use the higher-level concurrency utilities instead.
  • Standard collection interfaces as List, Queue, and Map manage their own synchronization internally. It is therefore impossible to exclude concurrent activity from a concurrent collection.
  • For example, use ConcurrentHashMap in preference to Collections.synchronizedMap or Hashtable. This increases performances.
  • Synchronizers are objects that enable threads to wait for one another, allowing them to coordinate their activities. The most commonly used synchronizers are CountDownLatch and Semaphore. Others include CyclicBarrier and Exchanger.
  • For interval timing, always use System.nanoTime in prefereence to System.currentTimeMillis.

Item 70: Document thread safety

  • The presence of the synchronized modifier in a method declaration is an implementation detail, not a part of its exported API. It does NOT indicate that a method is thread-safe.
  • To enable safe concurrent use, a class must clearly document what level of thread safety it supports:
    • immutable
    • unconditionally thread-safe. E.g. Random and ConcurrentHashMap
    • conditionally thread-safe. E.g. those collections returned by the Collections.synchronized wrappers, whose iterators require external synchronization.
    • not thread-safe. E.g. ArrayList, HashMap
    • thread-hostile. Very few Java methods are thread-hostile.

Item 71: Use lazy initialization judiciously

Lazy initialization advice include “don’t initialize unless you need to” (item 55). It increases the risk of accessing the lacily-initialized field.

  • If you use lazy initialization to break an initialization circularity, use a synchronized accessor.
  • If you need to use lazy initialization for performance on a static field, use the lazy initialization holder class idiom (a.k.a. initialize on-demand holder class idiom).
  • If you need to use lazy initialization for performance on an instance field, use the double-check idiom. Check a volatile field first time without locking; if it is not initialized, lock it, check again (key here! The double check!) and initialize it.

Item 72: Don’t depend on the thread scheduler

Any program that relies on the thread scheduler for correctness or performance is likely to be nonportable.
Advice: ensure the number of runnable threads is not significantly greater than the number of processors.
Note: Thread.yield() is among the least portable Java methods.

Item 73: Avoid thread groups

They are obsolete.

Serialization

Item 74: Implement Serializable judiciously

  • Implementing Serializable decreases the flexibility to change a class’s implementation once it has been released.
  • Implementing Serializable increases the likelihood of bugs and security holes.
  • It increases the testing burden associated with releasing a new version of a class.
  • Classes edsigned for inheritance should reraly implement Serializable, and interfaces should rarely extend it.
  • (Personal experience) If an inner class is serialized, its outer class should be serializable (because of an implicit self reference). If a class is Serializable, then all of its inner classes should be Serializable, too. Therefore, (non-static) inner classes should not implement Serializable.

Item 75: Consider using a custom serialized form

Don’t accept the default serialized form without first considering whether it is appropriate — it is likely to be appropriate if the physical representation is identical to its logical content. Even if you decide it is appropriate, you often must provide a readObject method to ensure invariants and security.

  • Using the default form ties the exported API to the current internal representation, may consume excessive space, time, and may cause stack overflows.

Item 76: Write readObject methods defensively

When an object is deserialized, defensively copy any field containing an object reference that a client must not process.

Item 77: For instance control, prefer enum types to readResolve

  • Any readObject method, whether explicit or default, returns a newly created instance.
  • readResolve allows you to substitute another instance for the one created by readObject. However, is you depend on readResolve for instance control, all instance fields with object reference types must be declared transient.
  • use enum to enforce instance control invariants wherever possible.

Item 78: Consider serialization proxies instead of serialized instances