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:
- Use
"bikini"
instead ofnew String("bikini")
- Use
Pattern.compile
instead ofString#match(regex)
- Prefer primitives (
long
) to boxed primitives (Long
)
Item 7: Eliminate obsolete object references
Unintentional object reference retention excludes the object from garbage collection, thus cause memory leaks.
Tips:
- Be alerted when a class manages its own memory.
- Use “weak reference” (e.g.,
WeakHashMap
).
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
:
- Reflexive: For any non-null reference value
x
,x.equals(x)
must return true. - Symmetric: For any non-null reference values
x
andy
,x.equals(y)
==y.equals(x)
. - Transitive: For any non-null reference values
x
,y
,z
, ifx.equals(y)
andy.equals(z)
, thenx.equals(z)
. - Consistent: For any non-null reference values
x
andy
,x.equals(y)
must consistently returntrue
or consistently returnfalse
. - Non-nullity: For any non-null reference value
x
,x.equals(null)
must returnfalse
.
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
- if the constants are strongly tied to an existing class or interface, add them there.
- if the constants are best viewed as members of an enumerated type, export them with an enum type. (item 34)
- 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
- suppress it with
@SuppressWarnings("unchecked")
on the smallest scope and, - 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.,
- does not write to the
varargs
parameter array, and - 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.
- typesafe (returns the the right type)
- heterogeneous (all keys are of different types).
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
- int enum constants (Item 34), and
- no easy way to iterate over all the elements, and
- the maximum number of bits is limited by the int type.
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:
- typo errors result in silent failures.
- no way to ensure they are used only on appropriate program elements.
- 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
- marker interface can catch error at compile time while marker annotation (item 39) can catch error at run time,
- 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.