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.Disadvantages include:
1
Map<String, Integer> m = HashMap.newInstance();
- 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 thefinally
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 invokingsuper.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:
- Don’t provide mutators (methods modifying the class’s state)
- Ensure the class cannot be extended.
- Either making this class final, or
- Making all its constructors private or package-private.
- Make all fields final.
- Make all fields private.
- 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
20public 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; }
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 | // Bit field enumeration constants - Don't use it! |
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 thanHashMap
.- To iterate over an
EnumMap
, use something likefor (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
11public 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
ordouble
. - 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 thehoisting
mechanism — use an explicit method to poll, to prevent JVM to transform into awhile(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
Item 69: Prefer concurrency utilities to wait
and notify
- Given the difficulty of using
wait
andnotify
correctly, you should use the higher-level concurrency utilities instead. - Standard collection interfaces as
List
,Queue
, andMap
manage their own synchronization internally. It is therefore impossible to exclude concurrent activity from a concurrent collection. - For example, use
ConcurrentHashMap
in preference toCollections.synchronizedMap
orHashtable
. 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 areCountDownLatch
andSemaphore
. Others includeCyclicBarrier
andExchanger
.- For interval timing, always use
System.nanoTime
in prefereence toSystem.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
andConcurrentHashMap
- 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 isSerializable
, then all of its inner classes should beSerializable
, too. Therefore, (non-static) inner classes should not implementSerializable
.
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 byreadObject
. However, is you depend onreadResolve
for instance control, all instance fields with object reference types must be declared transient.- use
enum
to enforce instance control invariants wherever possible.