- Types: interfaces (annotations), classes, arrays, primitives.
- A class's members: fields, methods, member classes, member interfaces.
- A method's signature: name and the type of its formal parameters.
- Consider static factory methods instead of constructors
- Consequences
- => they have name
- => instance control (not required to create a new object, can return an object of any subtype of their return type, the returned object can vary from call to call as a function of the input parameters, the class of the returned object need not exist when the class containing the method is written)
- => classes providing only static factory methods cannot be subclassed.
- => hard for programmers to find.
- Implementation
- common names:
from
,of
,valueOf
,instance
orgetInstance
,create
ornewInstance
,getType
,newType
,type
- common names:
- Applicability
- often preferable.
- Consequences
- Consider a builder when faced with many constructor parameters
- Motivation
- static factories, constructors => do not scale well to large numbers of optional parameters.
- Telescoping constructor pattern => hard to read and write, do not scale well.
- JavaBeans Pattern => allows inconsistency (through its construction), mandates mutability.
- Consequences
- => simulate named optional parameters => easy to read and write.
- => check invariants in the builder's constructor.
- => well suited to class hierarchies => use a parallel hierarchy of builders, each nested in the corresponding class.
- => creating the builder could be a problem in performance-critical situations.
- Applicability
- use only if there are four or more parameters.
- better to start with a builder in the first place.
- Motivation
- Enforce the singleton property with a private constructor or an enum type
- Motivation
- represent either a stateless object or a intrinsically unique system component
- Consequences
- => a class is instantiated exactly once.
- => make it difficult to test its clients.
- Implementation
- singleton with public final field.
- => error-prone to accessibility attack.
- singleton with static factory.
- => difficult to
implements serializable
=> must declare all fieldstransient
and provide areadResolve
method.
- => difficult to
Enum
singleton is the preferred approach.- => consice, provides the serialization machinery for free.
- => cannot extend a superclass other than
Enum
.
- singleton with public final field.
- Motivation
- Enforce noninstantiability with a private constructor
- Motivation
- utility classes were not designed to be instantiated.
- Consequences
- a class can be made noninstantiable by including a private constructor => suppress default constructor.
- Implementation
- Throw
AssertionError
optionally.
- Throw
- Motivation
- Prefer dependency injection over hardwiring resources
- Motivation
- many classes depend on one or more underlying resources.
- Consequences
- => provides flexibility and testability.
- => preserves immutability of shared dependent objects.
- Implementation
- pass the resource into the constructor when creating a new instance.
- pass a resource factory (the Factory Method pattern) to the constructor.
Supplier<T>
interface => using a bounded wildcard typeSupplier<T extends XXX>
as the factory's type parameter => can create any subtype of a specified type.
- Motivation
- Avoid creating unnecessary objects
- Motivation
- reuse => faster and more stylish.
- Implementation
- using static factory methods.
- cache "expensive object".
- e.g.,
String.matches
=>Pattern
- e.g.,
- prefer primitives to boxed primitives, and watch out for unintentional autoboxing.
- Consequences
- maintaining your own object pool => clutters your code, increases memory footprint, harms performance.
- failing to make defensive copying => bugs and security holes.
- Motivation
- Eliminate obsolete object references
- Implementation
- null out references once they become obsolete.
- => immediately fail with a
NullPointerException
rather than failing quietly. - => should be the exception rather than the norm.
- => immediately fail with a
- let the variable that contained the reference fall out of scope.
- => define each variable in the narrowest possible scope.
- heap profiler => aid code inspection and debugging.
- null out references once they become obsolete.
- Consequences
- managing its own memory => memory leaks.
- => null out references when an element is freed.
- caches => memory leaks.
- => clean entries that have fallen into disuse.
- listeners and callbacks => memory leaks
- => storing only weak references.
- managing its own memory => memory leaks.
- Implementation
- Avoid finalizers and cleaners
- Motivation
- finalizers => unpredictable, dangerous, unnecessary.
- clearners => less dangerous than finalizers, but still unpredictable, slow, unnecessary.
- Applicability
- never do anything time-critical in a finalizer or cleaner.
- never depend on a finalizer or cleaner to update persistent state.
- Consequences
- finalizer => an uncaught exception thrown during finalization is ignored, and finalization of that object terminates.
- => severe performance penalty.
- => open your class up to finalizer attacks.
- => must write a final
finalize
method to protect nonfinal classes.
- => must write a final
- Implementation
- Just have your class implement
AutoCloseable
.- => invoke
close
on each no longer needed instance. - => use
try
-with-resource clause to ensure termination even in the face of exceptions. - => keep track of whether it has been closed and throw
IllegalStateException
exceptionally.
- => invoke
- Just have your class implement
- Motivation
- Prefer
try
-with-resources totry
-finally
- Motivation
- many resource must be closed by invoking a
close
method. try
-finally
is ugly when used with more than one resource.
- many resource must be closed by invoking a
- Implementation
- Implement the
AutoCloseable
interface => a singlevoid
-returningclose
method.
- Implement the
- Motivation
- Obey the general contract when overriding
equals
- Applicability
- No need to override
equal
if each instance is equal only to itself.- Each instance of the class is inherently unique.
- No need for the class to provide a "logical equality" test.
- A superclass has already overridden
equals
, and the superclass behavior is appropriate for this class. - The class is private or package-private, and you are certain that its
equals
method will never be invoked.
- No need to override
- Consequences
- General contract => Reflexive, Symmetric, Transitive, Consistent, Non-nullity.
- Implementation
- Use the
==
operator to check if the argument is a reference to this object. - Use the
instanceof
operator to check if the argument has the correct type. - Cast the argument to the correct type.
- For each "significant" field in the class, check if that field of the argument matches the corresponding field of this object.
- Always override
hashCode
when you overrideequals
. - Don't substitute another type for
Object
in theequals
declaration.- => parameter type must be
Object
.
- => parameter type must be
- Use the
- Applicability
- Always override
hashCode
when you overrideequals
- Motivation
- equal objects must have equal hash codes.
- Implementation
result = 31 * result + c;
Objects.hash
=> run more slowly.- might consider caching the hash code in the object if the cost is significant.
- do not be tempted to exclude siginifcant fields from the hash code computation to improve performance.
- don't provide a detailed specification for the value returned by
hashCode
=> so clients can't reasonably depend on it => flexible to change it.
- Motivation
- Always override
toString
- Motivation
- default implementation: "at sign" (
@
) and the unsigned hexadecimal representation of the hash code => not what user expect.
- default implementation: "at sign" (
- Consequences
- => makes your class much more pleasant to use and makes systems using the class easier to debug.
- =>
toString
returns all of the interesting information contained in the object. - Failing to provide accessors => turn the string format into a de facto API.
- Implementation
Returns the string representation of this XXX.
- Whether or not you decide to specify the format, you should clearly document your intentions.
- Specifying the format => standard, unambiguous, human-readable representation, unable to change.
- Not specifying the format => subject to change.
- Motivation
- Override
clone
judiciously- Motivation
Cloneable
: a mixin interface for classes to advertise that they permit cloning.- It lacks a
clone
method, andObject
'sclone
method is protected. - Even a reflective invocation may fail, because there is no guarantee that the object has an accessible
clone
method. Object
'sclone
method returns a field-by-field copy or throwsCloneNotSupportedException
.
- It lacks a
- Consequences
- A class implementing
Cloneable
is expected to provide a properly functioning publicclone
method. - Immutable classes should never provide a
clone
method. - In effect, the
clone
method functions as a constructor.- => ensure that it does no harm to the original object and it properly establishes invariants on the clone.
- Calling
clone
on an array returns an array whose runtime and compile-time types are identical to those of the array being cloned.
- Like serialization, the
Cloneable
architecture is incompatible with normal use of final fields referring to mutable objects.- => must support a "deep copy" method.
- A class implementing
- Implementation
- Override
clone
with a public method whose return type is the class itself.- First call
super.clone
, then fix any fields that need fixing.- Copying any mutable objects and replacing the clone's references to these objects with references to their copies.
- Serial number or unique ID need to be fixed.
- First call
- Public
clone
method should omit thethrows
clause.- => need not throw
CloneNotSupportedException
. - => methods don't throw checked exceptions are easier to use.
- => need not throw
- When designing a class for inheritance
- => implementing a properly functioning protected
clone
method that is declared to throwCloneNotSupportedException
. - =>
@Override protected final Object clone() throws CloneNotSupportedException { ... }
- => implementing a properly functioning protected
- When designing a thread-safe class
- =>
clone
method must be properly synchronized.
- =>
- A better approach to object copying is to provide a copy constructor or copy factory.
public Yum(Yum yum) { ... };
public static Yum newInstance(Yum yum) { ... };
- => don't rely on a risk-prone extralinguistic object creation mechanism.
- => don't demand unenforceable adherence to thinly documented conventions.
- => don't conflict with the proper use of final fields.
- => don't throw unnecessary checked exceptions.
- => don't require casts.
- Interface-based conversion constructor and conversion factories
- => allow the client to choose the implementation type of the copy.
- e.g.,
HashSet cs = new TreeSet<>(s)
- e.g.,
- => allow the client to choose the implementation type of the copy.
- Override
- Motivation
- Consider implementing
Comparable
- Consequences
- => natural ordering => easy to search, compute extreme values, and maintain automatically sorted collections.
- Implementation
compareTo
method: Compares this object with the specified object for order. Returns a negative integer, zero, or a positive as this object is less than, equal to, or greater than the specified object. ThrowsClassCastException
if the specified object's type prevents it from being compared to this object.- Invoke the
compareTo
method recursively.- Use of the relational operators
<
and>
incompareTo
method is verbose and error-prone and no longer recommended.
- Use of the relational operators
- comparator construction methods => using static imported comparator construction methods => simple names for clarity and brevity.
- e.g.,
comparingInt
,thenComparingInt
,Integer.compare
.
- e.g.,
- Consequences
-
Minimize the accessibility of classes and members
- Motivation
- information hiding or encapsulation => decoupling API from implementation => allow components to be developed, tested, optimized, used, understood, and modified in isolation.
- make each class or member as inaccessible as possible.
- Consequences
- accessibility (access control in Java)
- e.g.,
private
,protected
,public
, and package-private. - make it package-private => implementation.
- make it public => must maintain compatibility forever.
- e.g.,
- => overridden methods cannot have a more restrictive access level in the subclass.
- => should not raise the accessibility any higher than package-private to facilitate testing your code.
- module system => module declaration
- accessibility (access control in Java)
- Implementation
- Instance fields of public classes should rarely be public.
- public mutable fields => not thread-safe.
- expose public static final fields is an exception.
- It is wrong to have a public static final array field, or an accessor that returns such a field.
- => make the array private, and
- => add a public immutable list through
Collections.unmodifiableList
. - => or, add a public method tha returns a copy of private array.
- => add a public immutable list through
- => make the array private, and
- Instance fields of public classes should rarely be public.
- Motivation
-
In public classes, use accessor methods, not public fields
- Consequences
- => accessor methods for private fields and mutators for mutable classes.
- If a class is package-private or private nested class, there is nothing inherently wrong with exposing its data field.
- It is questionable for public classes to expose immutable fields.
- => it precludes changing the internal representation in a later release.
- Consequences
-
Minimize mutability
- Applicability
- Classes should be immutable unless there's a very good reason to make them mutable.
- If a class cannot be made immutable, limit its mutability as much as possible.
- Constructors should create fully initialized objects with all of their invariants established.
- Consequences
- => information contained in each instance is fixed for its lifetime.
- => simple => clear state space, precise state transitions.
- => inherently thread-safe; require no synchronization.
- => can be shared freely => never have to make defensive copies => need not provide a
clone
method or copy constructor. - => they can share their internals.
- => make greater building blocks for other objects.
- => provide failure atomicity for free.
- => they require a separate object for each distinct value => extra cost.
- Implementation
- To make a class immutable
- => Don't provide methods that modify the object's state (mutators).
- => Ensure that the class can't be extended.
- => Make all fields final.
- => Make all fields private.
- => Ensure exclusive access to any mutable components.
- Make defensive copies in constructors, accessors, and
readObject
methods.
- Make defensive copies in constructors, accessors, and
- To fix performance problem
- => guess multi-step operations will be commonly required and to provide them as primitives.
- => provide a public mutable companion class.
- To make immutability flexible
- => make all of its constructors private or package-private and add public static factories in place of the public constructors.
- => effectively final outside its package.
- => allow to tune the performance through object-caching.
- => only guarantee that no method may produce an externally visible change in the object's state.
- e.g., have nonfinal fields caching the result of expensive computations the first time they are needed.
- => make all of its constructors private or package-private and add public static factories in place of the public constructors.
- To make a class immutable
- Applicability
-
Favor composition over inheritance
-
Motivation
- inheritance
- => achieve code reuse, also override methods.
- => a subclass depends on the implementation details of its superclass for its proper function => violates encapsulation
- superclass can acquire new methods in subsequent releases.
- => dangerous to inherit across package boundaries.
- => propagate any flaws in the superclass's API.
- composition: declare a private field that references an instance of the existing class.
- inheritance
-
Consequences
-
=> forwarding => no dependencies on the implementation of the existing class.
- => write forwarding methods only once.
-
the combination of composition and forwarding => delegation.
-
=> not suited for use in callback frameworks.
- wrapper class objects pass self-references (
this
) to other objects for subsequent callback invocations and it doesn't know of its wrapper => SELF problem.
- wrapper class objects pass self-references (
-
-
Implementation
- Reusable forwarding class + Wrapper class extends forwarding class.
-
-
Design and document for inheritance or else prohibit it
- Implementation
- First, the class must document precisely the effects of overriding any method.
- The class must document its self-use of overridable methods.
- For each public or protected method, the documentation must indicate which overridable methods the method invokes, in what sequences, and how the results of each invocation affect subsequent processing.
@implSpec
: inner working of the method.
- To allow efficient subclasses, a class may have to provide hooks into its internal workings in the form of judiciously chosen protected methods.
- e.g.,
removeRange
method fromjava.util.AbstractList
=> provided solely to make it easy for subclasses to provide a fast helper.
- e.g.,
- Constructors must not invoke overridable methods, directly or indirectly.
- The superclass constructor runs before the subclass constructor => the overriding method in the subclass will get invoked before the subclass constructor has run.
- Neither
clone
norreadObject
may invoke an overridable method, directly or indirectly. - When implementing
Serializable
=> makereadResolve
orwriteReplace
method protected => avoid them to be ignored by subclasses.
- Eliminate a class's self-use of overridable methods mechanically.
- => move the body to a private helper method.
- First, the class must document precisely the effects of overriding any method.
- Consequences
- => The only way to test a class designed for inheritance is to write subclasses.
- => You must test your class by writing subclasses before you release it.
- => The best solution is to prohibit subclassing in classes that are not designed and documented to be safely subclassed.
- => declare the class final.
- => make all the constructors private or package-private and to add public static factories in place of the constructors.
- Implementation
-
Prefer interfaces to abstract classes
- Motivation
- interfaces versus abstract classes.
- both can provide implementations for instance methods.
- abstract class => single inheritance => must place common abstract class high up => damage to the type hierarchy.
- Existing classes can easily be retrofitted to implement a new interface.
- Interfaces are ideal for defining mixins.
- => provide optional behavior.
- Interfaces allow for the construction of nonhierarchical type frameworks.
- Interfaces enable safe, powerful functionality enhancements via the wrapper class idiom.
- interfaces versus abstract classes.
- Consequences
- abstract skeletal implementation class
- => combine the advantages of interfaces and abstract classes .
- => the interface defines the type, providing default methods.
- => the skeletal implementation class implements the remaining non-primitive interface mthods atop the primitive interface methods.
- => extending a skeletal implementation.
- skeletal implementation classes are called
AbstractInterface
.
- simple implementation
- it isn't abstract; it is the simplest possible working implementation.
- e.g.,
AbstractMap.SimpleEntry
.
- abstract skeletal implementation class
- Motivation
-
Design interfaces for posterity
- Consequences
- default method => a default implementation can be used by all classes that implement the interface.
- => not always possible to write a default method that maintains all invariants of every conceivable implementation.
- => existing implementations of an interface may compile without error or warning but fail at runtime.
- Applicability
- It is of the utmost importance to design interfaces with great care.
- You cannot count on correcting interface flaws after an interface is released.
- Consequences
-
Use interfaces only to define types
- Applicability
- the interface serves as a type that can be used to refer to instances of the class.
- Consequences
- constant interface antipattern
- => consists solely of static final fields, each exporting a constant.
- => leak implementation detail into the class's exported API.
- => if the constants are strongly tied to an existing class or interface, you should add them to the class or interface.
- => export them with an enum type.
- => export the constants with a noninstantiable utility class,
- => use static import facility.
- constant interface antipattern
- Applicability
-
Prefer class hierarchies to tagged classes
- Motivation
- tagged classes: contain a tag field indicating the flavor of the instance.
- => verbose, error-prone, and inefficient.
- tagged classes: contain a tag field indicating the flavor of the instance.
- Implementation
- First, define an abstract class containing an abstract method for each method in the tagged class whose behavior depends on the tag vlaue.
- Next, define a concrete subclass of the root class for each flavof of the original tagged class.
- Also include in each subclass the appropriate implementation of each abstract method in the root class.
- Motivation
-
Favor static member classes over nonstatic
- Applicability
- A nested class should exist only to serve its enclosing class.
- If you declare a member class that does not require access to an enclosing instance, always put the
static
modifier in its declaration.
- Consequences
- static member classes
- => can function as public helper class, useful only in conjunction with its outer class.
- e.g., Clients of
Calculator
could refer to operations using public static member enum classCalculator.Operation
.
- e.g., Clients of
- => can function as public helper class, useful only in conjunction with its outer class.
- private static member classes
- => can represent components of the object represented by their enclosing classes.
- e.g.,
Map
's internalEntry
object.- => while each entry is associated with a map, the methods on the entry do not need to access to the map.
- e.g.,
- => can represent components of the object represented by their enclosing classes.
- nonstatic member class
- => implicitly associated with an enclosing instance of its containing class => takes up space in the nonstatic member class instance and adds time to its construction.
- => you can invoke methods on the enclosing instance or obtain a reference to the enclosing instance using the qualified
this
construct. - => can be used to define an Adapter.
- e.g.,
Map
's collection views methodskeySet
,entrySet
, andvalues
. - e.g.,
Set
andList
typically use nonstatic classes to implement their iterators.
- e.g.,
- anonymous class
- => has no name.
- => not a member of its enclosing class.
- => cannot have any static members other than constant variables, which are final primitive or string fields.
- => can create small function objects and process objects on the fly.
- => but lambdas are now preferred.
- => can use in the implementation of static factory methods.
- local class
- => declared anywhere a local variable can be declared.
- => should be kept short so as not to harm readability.
- static member classes
- Applicability
-
Limit source files to a single top-level class
- Motivation
- Defining multiple top-level classes in a source file => possible to provide multiple definitions for a class => affected by the order in which the source files are passed to the compiler.
- Implementation
- Use static member classes instead of multiple top-level classes.
- Applicability
- Never put multiple top-level classes or interfaces in a single source file.
- Motivation
- Don't use raw types
- Applicability
- generic: a class or interface whose declaration has one or more type parameters.
- parameterized types: e.g.,
List<String>
: a list of parameterized typeString
. - ray type: the name of the generic type used without accompanying type parameters.
- Consequences
- Each generic type defines a raw type.
- => behave as if all of the generic type information were erased from the type declaration.
- => exist primarily for compatibility with pre-generics code.
- If you use raw types, you lose all the safety and expressiveness benefits of generics.
- Using raw types can lead to exceptions at runtime; parameterized collection type ensures compile-time check.
- Each generic type defines a raw type.
- Implementation
- To allow insertion of arbitrary objects, use unbounded wildcard types.
- If you want to use a generic type but you don't know or care what the actual type parameter is, use a question mark instead.
- You can't put any element (other than
null
) into aCollection<?>
. - e.g.,
Set<E>
=>Set<?>
containing only objects of some unknown type. - use generic methods or bounded wildcard types if you can assume about the type of the objects.
- Exceptions to the rule that you should not use raw types.
- You must use raw types in class literals.
- e.g.,
List.class
instead ofList<String>.class
.
- e.g.,
- It is legal to use the
instanceof
operator on parameterized types.
- You must use raw types in class literals.
- To allow insertion of arbitrary objects, use unbounded wildcard types.
- Applicability
- Eliminate unchecked warnings
- Applicability
- Eliminate every unchecked warning that you can.
- Consequences
- => ensure that your code is type safe => you won't get a
ClassCastException
at runtime.
- => ensure that your code is type safe => you won't get a
- Implementation
- If you can prove that the code that provoked the warning is type safe, then (and only then) suppress the warning with an
@SuppressWarnings("unchecked")
annotation.- Always use the
SuppressWarnings
annotation on the smallest scope possible. - Every time you use a
@SuppressWarnings("unchecked")
annotation, add a comment saying why it is safe to do so.
- Always use the
- If you can prove that the code that provoked the warning is type safe, then (and only then) suppress the warning with an
- Applicability
- Prefer lists to arrays
- Motivation
- Arrays are covariant =>
Sub[]
is a subtype of the arraySuper[]
. - Generics are invariant =>
List<Type1> is neither a subtype nor a supertype of
List<Type2`. - Arrays are reified. => arrays know and enforce their element type at runtime.
- e.g., get an
ArrayStoreException
if putting aString
into an array ofLong
.
- e.g., get an
- Generics are implemented by erasure => they enforce their type constraints only at compile time and discard their element type at runtime.
- => arrays and generics do not mix well.
- It is illegal to create an array of a generic type, a parameterized type, or a type parameter.
- e.g., illegal creation expressions:
new List<E>[]
,new List<String>[]
,new E[]
.
- Arrays are covariant =>
- Consequences
- use the collection type
List<E>
in preference to the array typeE[]
.- => might sacrifice some conciseness or performance, in exchange for better type safety and interoperability.
- use the collection type
- Motivation
- Favor generic types
- Motivation
- We can often generify programs without harming clients of the original non-parameterized version.
- Generic types are safer and easier to use than types that require casts in client code.
- Implementation
- some technique for eliminating the generic array creation may cause heap pollution => the rumtime type of the array does not match its compile-time type.
- some generic types that restrict the permissible values of their type parameters => bounded type parameter.
- e.g.,
class DelayQueue<E extends Delayed> implements BlockingQueue<E>
- e.g.,
- Motivation
- Favor generic methods
- Consequences
- declare a type parameter => make the method typesafe.
- Implementation
- the type parameter list goes between a method's modifiers and its return type.
- e.g.,
public static <E> Set<E> union(Set<E> s1, Set<E> s2);
.
- e.g.,
- generic singleton factory: a static factory method to repeatedly dole out the object for each requested type parameterization.
- e.g.,
Collections.reverseOrder
,Collections.emptySet
.
- e.g.,
- recursive type bound => wildcard variant, the simulated self-type idiom.
- e.g.,
public static <E extends Comparable<E>> E max(Collection<E> c);
.
- e.g.,
- the type parameter list goes between a method's modifiers and its return type.
- Consequences
- Use bounded wildcards to increase API flexibility
- Consequences
- PECS stands for producer-
extends
, consumer-super
. - Do not use bounded wildcard types as return types.
- PECS stands for producer-
- Implementation
- bounded wildcard type
<? extends E>
: wildcard type for a parameter that serves as anE
producer.<? super E>
: wildcard type for a parameter that serves as anE
consumer.
- Comparables are always consumers.
- generally use
Comparable<? super T>
andComparator<? super T>
. - e.g.,
public static <T extends Comparable<? super T>> T max(List<? extends T> list);
.
- generally use
- If a type parameter appears only once in a method declaration, replace it with a wildcard.
- Write a private helper method to capture the wildcard type.
- bounded wildcard type
- Consequences
- Combine generics and varargs judiciously
- Motivation
- varargs => leasy abstraction => an array is created to hold the varargs parameters and it is visible.
- If a method declares its varargs parameter a generic or parameterized types => warning on the declaration => possible heap pollution.
- Consequences
- It is unsafe to store a value in a generic varargs array parameter.
- It is unsafe to give another method access to a generic varargs parameter array.
- Use
@SafeVarargs
on every method with a varargs parameters of a generic or parameterized type => never write unsafe varargs methods. - An alternative is to replace the varargs parameter with a
List
parameter.- => the compiler can prove that the method is typesafe.
- => the author does not have to vouch for its safety with a
SafeVarargs
annotation. - => the client code is a bit verbose and may be a bit slower.
- Implementation
@SafeVarargs
annotation => the author promise it is typesafe => allow the author of a method with a generic varargs parameter to suppress client warnings automatically.- If the method doesn't store anything into the array and doesn't allow a reference to the array to escape, then it's safe.
- Motivation
- Consider typesafe heterogeneous containers
- Motivation
- Limitations to fixed numbers of type parameters per container.
- Consequences
- type token: a class literal passed among methods to communicate both compile-time and runtime type information.
- bounded type token: a type token that places a bound on type.
- placing the type parameter on the key rather than the container => customer key type => unfixed number of type parameters per contains.
- Implementation
- typesafe heterogeneous container => e.g.,
public <T> void putFavorite(Class<T> type, T instance);
- dynamic cast => e.g.,
favorites.put(Objects.requireNonNull(type), type.cast(instance));
- typesafe heterogeneous container => e.g.,
- Motivation
- Use enums instead of
int
constants- Motivation
int
enum pattern => severaly deficient.- => no type safety, little expressive power.
- => clients must recompile if the value associated with an
int
enum is changed. - => no way to translate
int
enum constants into printable strings.
- Consequences
- enum type
- => instance-controlled => export one instance for each enumeration constant via a public static final field => a generalization of singletons.
- => flexible to add arbitrary methods and fields and implement arbitrary interfaces.
- => be their nature immutable.
- Use enums any time you need a set of constants whose memebers are known at compile time.
- enum type
- Implementation
- To associate data with enum constants, declare instance fields and write a constructor that takes the data and stores it in the fields.
- Declare an abstract
apply
method in the enum type, and override it with a concrete method for each constant in a constant-specific class body.- => consider the strategy enum pattern if some, but not all, enum constants share common behaviors.
- Consider writing a
fromString
method to translate the string representation back to the corresponding enum.
- Motivation
- Use instance fields instead of ordinals
- Motivation
ordinal
method => returns the numerical position of each enum constant in its type.- Abuse of ordinal to derive an associated value.
- Consequences
- Never derive a value associated with an enum from its ordinal; store it in an instance field instead.
- Most programmers will have no use for the
ordinal
method.
- Motivation
- Use
EnumSet
instead of bit fields- Motivation
- bit field enumeration constants - OBSOLETE!
- => allow bitwise
OR
operation to combine constants into a set. - => hard to interpret a bit field and iterate over all of the elements.
- => you have to predict the maximum number of bits and choose a proper type for the bit field.
- => allow bitwise
- bit field enumeration constants - OBSOLETE!
- Consequences
EnumSet
- => conciseness, performance.
- => a rich set of static factories for easy set creation => e.g.,
EnumSet.of
.
- Implementation
- Take a
Set
rather than anEnumSet
=> accept the interface type rather than the implementation type.
- Take a
- Motivation
- Use
EnumMap
instead of ordinal indexing- Consequences
- Use
EnumMap
to associate data with an enum.- => very fast
Map
implementation designed for use with enum keys.
- => very fast
- Do not use the
ordinal
method to index array of arrays.
- Use
- Implementation
- e.g.,
Map<Plant.LifeCycle, Set<Plant>> plantsByLifeCycle = new EnumMap<>(Plant.LifeCycle.class);
. - the
EnumMap
constructor takes theClass
object of the key type: a bounded type token, which provides runtime generic type information. - Use a stream and an
EnumMap
to associate data with an enum.- e.g.,
Arrays.stream(garden).collect(groupingBy(p -> p.lifeCycle, () -> new EnumMap<>(LifeCycle.class), toSet()))
.
- e.g.,
- Use a nested
EnumMap
to associate data with enum pairs.
- e.g.,
- Consequences
- Emulate extensible enums with interfaces
- Motivation
- It is confusing that elements of an extension type are instances of the base type and not vice versa => no good way to enumerate over all of a base type and its extensions.
- operation codes (opcodes) => extensible enumerated types.
- Consequences
- Emulate extensible enum type by writing an interface.
- => clients implement the interface to extend their own enums.
- => implementations cannot be inherited from one enum type to another.
- => encapsulate shared functionality in a helper class or a static helper method.
- Implementations
<T extends Enum<T> & Operation>
ensures that theClass
object (Class<T>
) represents both an enum and a subtype ofOperation
.
- Emulate extensible enum type by writing an interface.
- Motivation
- Prefer annotations to naming patterns
- Motivation
- naming patterns => indicate some program elements demanded special treatment.
- e.g., JUnit testing framework requires test methods by begining their names with characters
test
. - => typographical errors result in silent failures.
- => no way to ensure that they are used only on appropriate program elements.
- => provide no good way to associate parameter values with program elements.
- e.g., JUnit testing framework requires test methods by begining their names with characters
- naming patterns => indicate some program elements demanded special treatment.
- Consequences
- Annotations
- marker annotation: has no parameters but simply marks the annotated element.
- class literals can be used as the values for annotation parameter.
- repeatable annotation type
- There is simply no reason to use naming patterns when you can use annotations instead.
- All programmers should use the predefined annotation types that Java provides.
- Annotations
- Motivation
- Consistently use the
Override
annotation- Consequences
- Use the
Override
annotation on every method declaration that you believe to override a superclass declaration. - It is good practice to use
Override
on concrete implementations of interface methods to ensure that the signature is correct. - In concrete classes, you need not annotate methods that you believe to override abstract method declarations.
- Use the
- Consequences
- Use marker interfaces to define types
- Motivation
- marker interface: contains no method declarations but merely designates a class that implements the interface as having some property.
- Consequences
- marker interfaces
- => define a type that is implemented by instance of the marked class
- => catch errors at compile time although some APIs do not take advantage of the interface => e.g.,
ObjectOutputStream.write
takes typeObject
instead ofSerializable
.
- => catch errors at compile time although some APIs do not take advantage of the interface => e.g.,
- => they can be targeted more precisely.
- => define a type that is implemented by instance of the marked class
- marker annotations
- => they are part of the larger annotation facility => consistency in annotation-based frameworks.
- => can be applied to any program element.
- marker interfaces
- Motivation
- Prefer lambdas to anonymous classes
- Motivation
- function objects: represent functions or actions.
- anonymous class: creates a function object.
- => adequate for Strategy pattern.
- functional interfaces: with a single abstract method, deserve special treatment.
- => allows to create instances of these interfaces using lambda expression.
- Consequences
- Omit the types of all lambda parameters unless their presence makes your program clearer.
- Lambdas lack names and documentation; if a computation isn't self-explanatory, or exceeds a few lines, don't put it in a lambda.
- You should rarely, if ever, serialize a lambda.
- => instead, using an instance of a private static nested class.
- Don't use anonymous classes for function objects unless you have to create instances of types that aren't functional interfaces.
- Motivation
- Prefer method references to lambdas
- Consequences
- method references => more succinct than lambdas.
- Where method references are shorter and clearer, use them; where they aren't, stick with lambdas equivalent.
- Implementation
- e.g.,
map.merge(key, 1, Integer::sum);
rather thanmap.merge(key, 1, (count, incr) -> count + incr);
- Method Reference Type: Static, Bound, Unbound, Class Constructor, Array Constructor.
- e.g.,
- Consequences
- Favor the use of standard functional interfaces
- Consequences
java.util.function
package provides a large collection of standard functional interfaces for your use.- If one of the standard functional interfaces does the job, you should generally use it in preference to a purpose-built functional interface.
- Don't be tempted to use basic functional interfaces with boxed primitives instead of primitive functional interfaces.
- You should seriously consider writing a purpose-built functional interface if
- It will be commonly used and could benefit from a descriptive name.
- It has a strong contract associated with it.
- It would benefit from custom default methods.
- Always annotate your functional interfaces with the
@FunctionalInterface
annotation.
- Implementations
- The six basic functional interfaces:
UnaryOperator<T>
=>T apply(T t)
BinaryOperator<T>
=>T apply(T t1, T t2)
Predicate<T>
=>boolean test(T t)
Function<T,R>
=>R apply(T t)
Supplier<T>
=>T get()
Consumer<T>
=>void accept(T t)
- Three variants of each of the six basic interfaces to operate on the primitive types
int
,long
,double
. - Nine variants of the
Function
for use when the result type is primitive orObject
(Obj) => prefixFunction
withSrcToResult
. - Two-argument versions:
BiPredicate<T,U>
,BiFunction<T,U,R>
,BiConsumer<T,U>
. BiFunction
variants returning the three primitive types:ToIntBiFunction<T,U>
,ToLongBiFunction<T,U>
,ToDoubleBiFunction<T,U>
BooleanSupplier
- The six basic functional interfaces:
- Consequences
- Use streams judiciously
- Motivation
- iterative code using code blocks, stream pipelines using function objects.
- streams API => east the task of performing bulk operations, sequentially or in parallel.
- Consequences
- stream pipelines are evaluated lazily.
- => evaluation doesn't start until the terminal operation is invoked.
- => data elements that aren't required in order to complete the terminal operation are never computed.
- streams API is fluent => allow calls to be chained into a single expression.
- Overusing streams makes programs hard to read and maintain.
- In the absence of explicit types, careful naming of lambda parameters is essential to the readability of stream pipelines.
- Using helper methods is even more important for readability in stream pipelines than in iterative code.
- Refactor existing code to use streams and use them in new code only where it makes sense to do so.
- Good matches for using stream technique:
- Uniformly transform sequences of elements
- Filter sequences of elements
- Combine sequences of elements using a single operation
- Accumulate sequences of elements into a collection, perhaps grouping them by some common attribute
- Search a sequence of elements for an element satisfying some criterion
- stream pipelines are evaluated lazily.
- Implementation
- common stream source: collections, arrays, files, regular expression pattern matchers, pseudorandom number generators, and other streams.
- you should refrain from using streams to process
char
values.
- Motivation
- Prefer side-effect-free functions in streams
- Motivation
- pure function: one whose result depends only on its input => streams paradigm.
- Implementation
- The
forEach
operation should be used only to report the result of a stream computation, not to perform the computation. - Use collectors to gather the elements of a stream into a true
Collection
.- e.g.,
comparing
method takes key extraction function. - e.g.,
toList()
,toSet()
,toCollection(collectionFactory)
. - e.g.,
toMap(keyMapper, valueMapper)
and three-argument form supports dealing with key collisions. - e.g.,
groupingBy
returns collectors to produce maps that group elements into categories based on a classifier function. - downstream collector: produces a value from a stream containing all the elements in a category => e.g.,
toSet()
results in sets rather than lists as the values in a map. - e.g.,
joining
returns a collector that simply concatenates the elements.
- e.g.,
- The
- Motivation
- Prefer Collection to Stream as a return type
- Motivation
Stream
fails to extendIterable
=> workaround to iterate over a stream is to adapt fromStream<E>
toIterable<E>
.
- Consequences
Collection
interface is a subtype ofIterable
and has astream
method => provides both iteration abd stream access.Collection
or an appropriate subtype is generally the best return type fore a public, sequence-returning method.- Arrays also provide
Array.asList
andStream.of
methods.
- Arrays also provide
- Do not store a large sequence in memory just to return it as a collection.
Collection
has anint
-returningsize
method, which limits the length of the returned sequence toInteger.MAX_VALUE
.- => consider implementing a custom collection.
- Motivation
- Use caution when making streams parallel
- Consequences
- Do not parallel stream pipelines indiscriminately.
- Parallelizing a pipeline is unlikely to increase its performance if the source is from
Stream.iterate
, or the intermediate operationlimit
is used.
- Parallelizing a pipeline is unlikely to increase its performance if the source is from
- Performance gains from parallelism are best on streams over
ArrayList
,HashMap
,HashSet
, andConcurrentHashMap
instances; arrays;int
ranges; andlong
ranges.- they can be accurately and cheaply split into subranges of any desired sizes => spliterator =>
Stream.spliterator
orIterable.spliterator
. - they provide good-to-excellent locality of reference when processed sequentially => sequential element references are stored together in memory.
- they can be accurately and cheaply split into subranges of any desired sizes => spliterator =>
- The stream pipeline's terminal operation affects the effectiveness of parallel execution.
- Not only can parallelizing a stream lead to poor performance, including liveness failures; it can lead to incorrect results and unpredictable behavior (safety failures).
- Override the
spliterator
method and test the performance extensively.
- Override the
- Parallelzing a stream => strictly a performance optimization.
- It is possible to achieve near-linear speedup in the number of processor cores.
- Do not parallel stream pipelines indiscriminately.
- Implementation
- The best terminal operations for parralelism are reductions:
Stream
'sreduce
, or prepackagedmin
,max
,count
, andsum
, or short-circuiting operationsanyMatch
,allMatch
, andnoneMatch
. - If you are going to parallelize a stream of random numbers, start with a
SplittableRandom
instance.
- The best terminal operations for parralelism are reductions:
- Consequences
- Check parameters for validity
- Consequences
- check invalid parameter values => fail quickly and cleanly with an appropriate exception.
- Implementation
- For public and protected methods, use the Javadoc
@throws
tag to document the exception that will be thrown if a restriction on parameter values is violated.- e.g.,
IllegalArgumentException
,IndexOutOfBoundsException
,NullPointerException
.
- e.g.,
- The class-level comment applies to all parameters in all of the class's public method.
- The
Objects.requireNonNull
method is flexible and convenient. - Non public methods can check their parameters using assertion => throw
AssertionError
if they fail.
- For public and protected methods, use the Javadoc
- Consequences
- Make defensive copies when needed
- Consequences
- => make a defensive copy of each mutable parameter to the method or constructor => use the copy in place.
- => prevent from violating the invariants.
- => performance penalty.
- Implementation
- Defensive copies are made before checking the validity of the parameters, and the validity check is performed on the copies rather than on the originals => prevent from time-of-check/time-of-use attack.
- Return defensive copies of mutable internal fields.
- Do not use the
clone
method to make a defensive copy of a parameter whose type is subclassable by untrusted parties.
- Consequences
- Design method signatures carefully
- Consequences
- Choose method names carefully.
- => obey naming conventions.
- => be consistent with the broader consensus.
- => avoid long method names.
- Don't go overboard in providing convenience methods.
- => make a class difficult to learn, use, document, test, and maintain.
- When in doubt, leave it out.
- Avoid long parameter lists.
- Aim for four parameters or fewer.
- Long sequences of identically typed parameters are especially harmful.
- => break the method up into multiple methods.
- => create helper classes to hold groups of parameters.
- => adapt the Builder pattern from object construction to method invocation.
- For parameter types, favor interfaces over classes.
- Prefer two-element enum types to
boolean
parameters.- => make code easier to read and write, also easy to add more options.
- Choose method names carefully.
- Consequences
- Use overloading judiciously
- Motivation
- The choice of which overloading to invoke is made at compile time.
- Selection among overloaded methods is static, while selection among overridden methods is dynamic.
- Consequences
- Avoid confusing uses of overloading.
- A safe, conservation policy if never to export two overloadings with the same number of parameters.
- You can always give methods different names instead of overloading them.
- Do not overload methods to take different functional interfaces in the same argument position.
- If you are retrofitting an existing class to implement a new interface, you should ensure that all overloadings behave identically when passed the same parameters.
- Motivation
- Use varargs judiciously
- Motivation
- varargs methods: variable arity methods.
- => firstly creating an array, then putting the argument values into the array, and finally passing the array to the method.
- varargs methods: variable arity methods.
- Consequences
- => every invocation causes an array allocation and initialization => performance cost.
- Implementation
- To take one or more arguments, declare the method to take two parameters, one normal parameter of the specified type and one varargs parameter of this type.
- e.g.,
static int min(int firstArg, int... remainingArgs);
- e.g.,
- To take one or more arguments, declare the method to take two parameters, one normal parameter of the specified type and one varargs parameter of this type.
- Motivation
- Return empty collections or arrays, not nulls
- Motivation
- returning
null
for special-case => requires extra code in the client to handle the possibly null return value.
- returning
- Consequences
- Never return
null
in place of an empty array or collection.- => difficult to use and more prone to error, no performance advantages.
- Never return
- Implementation
- Returning the same immutable empty collection repeatedly => avoids allocating empty collection that harms performance.
- Return a zero-length array instead of
null
. - Do not preallocate the array passed to
toArray
in hopes of improving performance.
- Motivation
- Return optionals judiciously
- Motivation
- When unable to return a value
- => throw an exception => exceptions should be reserved for exceptional conditions, and is expensive.
- => return
null
=> clients must contain special-case code. - =>
Optional<T>
=> an immutable container for object reference that is empty or present.
- When unable to return a value
- Consequences
- => similar in spirit to check exceptions, they force the user to confront the fact there may be no value returned.
- => an
Optional
requires allocated and initialized => performance cost.
- Implementation
- Never return a
null
value from anOptional
-returning method. - To create optionals =>
Optional.empty()
,Optional.of(value)
,Optional.ofNullable(value)
. - Container types, including collections, maps, streams, arrays, and optionals should not be wrapped in optionals.
- To choose what action to take if the method can't return a value =>
orElse
,orElseThrow
,get
. - Never return an optional of a boxed primitive type.
- Instead, use
OptionalInt
,OptionalLong
,OptionalDouble
.
- Instead, use
- Never appropriate to use an optional as a key, value, or element in a collection or array.
- Never return a
- Motivation
- Write doc comments for all exposed API elements
- Motivation
- Javadoc generates API documentation automatically from source code => doc comments => the API to be usable.
- Consequences
- You must precede every exported class, interface, constructor, method, and field declaration with a doc comment.
- The doc comment for a method should describe succinctly the contract between the method and its client.
- with exception of methods designed for inheritance, the contract should say what the method does rather than how it does its job.
- preconditions =>
@throws
tags for unchecked exceptions,@param
for affected parameters. - postconditions => after invocation has completed successfully.
- side effect => observable change in state.
- Doc comments should be readable both in the source code and in the generated documentation.
- No two members or constructors in a class or interface should have the same summary description.
- Remember to document thread-safety (level) and serialization (form).
- Implementation
@param
for every parameter, followed by a noun phrase.@return
unless the method has a void return type, followed by a noun phrase.@throws
for every exception thrown by the method, whether checked or unchecked, should consist of the work "if", followed by a clause describing the exception conditions.{@code}
around the code fragment.this
refers to the object on which a method is invoked when it is used in the doc comment for an instance method.@implSpec
for self-use patterns for inheritance.- When documenting a generic type or method, be sure to document all type parameters.
- When documenting an enum type, be sure to document the contants.
- When documenting an annotation type, be sure to document any members.
- Motivation
- Minimize the scope of local variables
- Consequences
- => increase the readability and maintainability.
- => reduce the likelihood of error.
- Implementation
- Declare it where it is first used.
- Nearly every local variable declaration should contain an initializer.
- One exception concerns
try-catch
statements.
- One exception concerns
- Prefer
for
loops towhile
loops.- Also works for iterating when you need the iterator.
- Keep methods small and focused.
- Consequences
- Prefer for-each loops to traditional
for
loops- Consequences
- => eliminates chances to use the wrong variable.
- => no performance penalty.
- Implementation
- where you can't use for-each:
- Destructive filtering => an explicit iterator and call
remove
method, orCollections.removeIf
. - Tranforming => the list iterator or array index to replace the value of an element.
- Parallel iteration => all iterators or index variables advanced in lockstep.
- Destructive filtering => an explicit iterator and call
- you can iterate every object that implements
Iterable
.
- where you can't use for-each:
- Consequences
- Know and use the libraries
- Consequences
- => you take the advantage of the knowledge of the experts who wrote it and the experience of those who used it before you.
- => you don't have to waste your time writing ad hoc solutions, even if they are marginally related to your work.
- => their performance tends to improve over time, with no effort on your part.
- => they tend to gain functionality over time.
- => you place your code in the mainstream.
- Implementation
- It pays to keep abreast of additions in every major release.
- Every programmar should be familiar with the basics of
java.lang
,java.util
, andjava.io
, and their subpackages.
- Consequences
- Avoid
float
anddouble
if exact answers are required- Consequences
float
anddouble
types are particularly ill-suited for monetary calculations.- Use
BigDecimal
,int
,long
for monetary calculations.- => less convenient than primitive arithmetic type.
- => slower.
- Consequences
- Prefer primitive types to boxed primitives
- Consequences
- primitives have only their values, whereas boxed primitives have identities distinct from their values.
- primitives have only fully functional values, whereas each boxed primitive type has one nonfunctional value, which is
null
. - primitives are more time- and space-efficient than boxed primitives.
- Implementation
- Applying the
==
operator to boxed primitives is almost always wrong.- => use comparator or static compare methods.
- When you mix primitives and boxed primitives in an operation, the boxed primitive is auto-unboxed.
- => reduces the verbosity, but not the danger.
- => unboxing can throw a
NullPointerException
.
- Applying the
- Consequences
- Avoid strings where other types are more appropriate
- Consequences
- Strings are poor substitutes for
- other value types.
- enum types.
- aggregate types.
- capabilities.
- Used inappropriately
- => more cumbersome, less flexible, slower, more error-prone.
- Strings are poor substitutes for
- Consequences
- Beware the performance of string concatenation
- Consequences
+
- => convenient for generating a single line of output, or for a small, fixed-size object.
- strings are immutable => the contents of both are copied => time quadratic => this technique does not scale.
StringBuilder
- => store the statement under construction using
append
. - => much faster.
- => store the statement under construction using
- Consequences
- Refer to objects by their interfaces
- Consequences
- You should favor the use of interfaces over classes to refer to objects.
- e.g., parameters, return values, fields.
- => flexible to switch implementations.
- Refer to an object by a class if no appropriate interface exists.
- value classes such as
String
andBigInteger
. - fundamental types in class-based framework.
- classes that provide extra methods not found in the interface.
- value classes such as
- You should favor the use of interfaces over classes to refer to objects.
- Consequences
- Prefer interfaces to reflection
- Motivation
java.lang.reflect
- Given a
Class
object, you can obtainConstructor
,Method
, andField
instances => let you manipulate their undering counterparts reflectively, - => You lose all the benefits of compile-time type checking, including exception checking.
- => The code required to perform reflective access is clumsy and verbose.
- => Performance suffers.
- Given a
- Consequences
- There are a few sophisticated applications that require reflection.
- e.g., code analysis tools, dependency injection frameworks.
- If you have any doubts as to whether your application requires reflection, it probably doesn't.
- If you must use a class that is unavailable at compile time, you can create instances reflectively and access them normally via their interface or superclass.
- There are a few sophisticated applications that require reflection.
- Motivation
- Use native methods judiciously
- Motivation
- Java Native Interface (JNI) => call native methods written in native programming languages.
- => access platform-specific facilities.
- => seldom necessary.
- Java Native Interface (JNI) => call native methods written in native programming languages.
- Consequences
- It is rarely advisable to use native methods for improved performance.
- A single bug in the native code can corrupt your entire application.
- Motivation
- Optimize judiciously
- Motivation
- Strive to write good programs rather than fast ones.
- information hiding => localize design decisions => can be changed in the future.
- Strive to avoid design decisions that limit performance.
- e.g., APIs, wire-level protocols, and persistent data formats.
- Consider the performance consequences of your API design decisions.
- e.g., making a public type mutable => defensive copying.
- e.g., using inheritance rather than composition => artificial limits on the performance of the subclass.
- e.g., using an implementation type rather than interface => ties to a specific implementation.
- Strive to write good programs rather than fast ones.
- Implementation
- It is a very bad idea to wrap an API to achieve good performance.
- good API => good performance.
- Measure performance before and after each attempted optimization.
- profiling tools, microbenchmarking framework, performance model.
- It is a very bad idea to wrap an API to achieve good performance.
- Motivation
- Adhere to generally accepted naming conventions
- Motivation
- naming conventions: typographical and grammatical.
- Implementation
- Package or Module =>
org.junit.jupiter.api
,com.google.common.collect
. - Class or Interface =>
Stream
,FutureTask
,LinkedHashMap
,HttpClient
. - Method or Field =>
remove
,groupingBy
,getCrc
. - Constant Field =>
MIN_VALUE
,NEGATIVE_INFINITY
. - Local Variable =>
i
,denom
,houseNum
. - Type Parameter =>
T
,E
,K
,V
,X
,R
,U
,V
,T1
,T2
.
- Package or Module =>
- Motivation
- Use exceptions only for exceptional conditions
- Consequences
- Exceptions should never be used for ordinary control flow.
- A well-designed API must not force its clients to use exceptions for ordinary control flow.
- Implementation
- If one method can be invoked only under certain unpredictable conditions
- => provide a separate state-testing method.
- => have the state-dependent method return an empty optional or a distinguished value.
- If one method can be invoked only under certain unpredictable conditions
- Consequences
- Use checked exceptions for recoverable conditions and runtime exceptions for programming errors
- Consequences
- Use checked exceptions for conditions from which the caller can reasonably be expected to recover.
- => force the caller to handle the exception in a
catch
clause or to propagate it outward.
- => force the caller to handle the exception in a
- Use runtime exceptions to indicate programming errors.
- => indicate precondition violations by the client.
- => cause the current thread to halt with an appropriate error message.
- Errors are reserved for use by the JVM to indicate resource deficiencies, invariant failures, or other conditions that make it impossible to continue execution.
- All of the unchecked throwables you implement should subclass
RuntimeException
(directly or indirectly).- => you shouldn't define
Error
subclasses except forAssertionError
and you shouldn't throw them either.
- => you shouldn't define
- When in doubt, throw unchecked exceptions.
- Use checked exceptions for conditions from which the caller can reasonably be expected to recover.
- Implementation
- Provide methods for additional information concerning the condition that caused the exception to be thrown => aid in recovery.
- Parsing the string representation of an exception is an extremely bad practice.
- Consequences
- Avoid unnecessary use of checked exceptions
- Consequences
- A single checked exception
- => force programmers to deal with problems, enhance reliability.
- => must appear in a
try
block and can't be used directly in streams.
- A single checked exception
- Implementation
- Return an optional to eliminate a checked exception.
- => can't return any additional information detailing its inability to perform the desired computation.
- Break the method that throws the exception into two methods
- => newly declared state-testing method that returns a
boolean
indicating whether the exception would be thrown.
- => newly declared state-testing method that returns a
- Return an optional to eliminate a checked exception.
- Consequences
- Favor the use of standard exceptions
- Consequences
- => matches the established conventions => easier to learn and use.
- => no unfamiliar exceptions => easier to read.
- => smaller memory footprint and less time spent loading classes.
- Implementation
IllegalArgumentException
=> Non-null parameter value is inappropriate.NullPointerException
=> Parameter value is null where prohibited.IndexOutOfBoundsException
=> Index parameter value is out of range.
IllegalStateException
=> Object state is inappropriate for method invocation.ConcurrentModificationException
=> Concurrent modification of an object has been detected where it is prohibited.UnsupportedOperationException
=> Object does not support method.- Do not reuse
Exception
,RuntimeException
,Throwable
, orError
directly. - Throw
IllegalStateException
if no argument value would have worked, otherwise throwIllegalArgumentException
.
- Consequences
- Throw exceptions appropriate to the abstraction
- Implementations
- High layers should catch lower-level exceptions and, in their place, throw exceptions that can be explained in terms of the higher-level abstraction.
- exception chaining: the cause is passed to the higher-level exception via a chaining-aware constructor.
- While exception translation is superior to mindless propagation of exceptions from lower layers, it should not be overused.
- High layers should catch lower-level exceptions and, in their place, throw exceptions that can be explained in terms of the higher-level abstraction.
- Implementations
- Document all exceptions thrown by each method
- Consequences
- => forms a part of general contract.
- Implementation
- Always declare checked exceptions individually and document precisely the conditions under which each one is thrown using the Javadoc
@throws
tag. - It is wise to document unchecked exceptions as carefully as the checked exceptions => describes the preconditions effectively.
- Do not use the
throws
keyword on unchecked exceptions.
- Always declare checked exceptions individually and document precisely the conditions under which each one is thrown using the Javadoc
- Consequences
- Include failure-capture information in detail messages
- Consequences
- Program failure due to an uncaught exception => print out the stack trace.
- the exception's string representation: the exception's class name followed by its detail message.
- => probably the only information to investigate the software failure.
- Program failure due to an uncaught exception => print out the stack trace.
- Implementation
- To capture a failure, the detail message of an exception should contain the values of all parameters and fields that contributed to the exception.
- e.g.,
IndexOutOfBoundsException
=> contain the lower bound, upper bound, index value.
- e.g.,
- One cavaet concerns security-sensitive information.
- Do not include password, encryption keys, and the like in detail message
- User-level error messages must be intelligible to end users.
- To capture a failure, the detail message of an exception should contain the values of all parameters and fields that contributed to the exception.
- Consequences
- Strive for failure atomicity
- Consequences
- Generally speaking, a failed method invocation should leave the object in the state that it was in prior to the invocation => failure-atomicity.
- Implementation
- immutable objects => free failure atomicity.
- mutable objects
- => check parameters for validity before performing the operation.
- => order the computation so that any part that may fail takes place before any part that modifies the object.
- => perform the operation on a temporary copy of the object.
- => write recovery code and roll back its state to the point before the operation begin.
- Consequences
- Don't ignore exceptions
- Consequences
- ignore exceptions => an empty
catch
block => defeat the purpose of exceptions that forces you to handle exceptional conditions.
- ignore exceptions => an empty
- Implementation
- If you choose to ignore an exception, the
catch
block should contain a comment explaining why it is appropriate to do so, and the variable should be namedignored
.
- If you choose to ignore an exception, the
- Consequences
- Synchronize access to shared mutable data
- Consequences
- Without synchronization, one thread's changes might not be visible to other threads.
- Synchronization is required for reliable communication between threads as well as for mutual exclusion.
- Synchronization is not guaranteed to work unless both read and write operations are synchronized.
- safe publication: transferring effectively immutable objects from one thread to another.
- store it in a static field as part of class initialization.
- store it in a volatile field, a final field, a field that is accessed with normal locking.
- put it into a concurrent collection.
- The best way is not to share mutable data.
- Implementation
synchronized
=> only a single thread can execute a method or block at one time => mutual exclusion.- => multiple invocations won't be interleaved.
- => each invocation will see the effects of all previous invocations.
- Reading or writing a variable is atomic unless the variable is of type
long
ordouble
. - Stopping one thread from another.
- Do not use
Thread.stop
. - Have the first thread poll a
boolean
field initially asfalse
but set totrue
by the second thread to indicate that the first thread is to stop itself.- => potential liveness failure => synchronize access to the
boolean
field.
- => potential liveness failure => synchronize access to the
- Do not use
volatile
modifier => guarantees that any thread that reads the field will see the most recently written value.++
is not atomic => read-modify-write safety problem.- => use atomic variables or references from
java.util.concurrent.atomic
=> lock-free synchronization.
- => use atomic variables or references from
- Consequences
- Avoid excessive synchronization
- Consequences
- To avoid liveness and safety failures, never cede control to the client within a synchronized method or block.
- => do not invoke a method that is designed to be overridden, or one provided by a client in the form of a function object.
- Reentrant lock
- => simplify the construction of multi-threaded programs.
- => may turn liveness failures into safety failures.
- Move the alien method invocations out of the synchronized block => open calls.
- => prevent failures, increase concurrency.
- e.g.,
CopyOnWriteArrayList
: concurrent collection, iteration requires no locking, fast.
- As a rule, you should do as little work as possible inside synchronized regions.
- Excessive synchronization
- => contention => lost parallelism and delays to ensure that every core has a consistent view of memory.
- => limit the VM's ability to optimize code execution.
- When in doubt, do not synchronize your class, but document that it is not thread-safe.
- To avoid liveness and safety failures, never cede control to the client within a synchronized method or block.
- Consequences
- Prefer executors, tasks, and streams to threads
- Consequences
- Executor Framework => flexible interface-based task execution facility.
- task: unit of work =>
Runnable
andCallable
. - execution mechanism => executing tasks with executor service.
java.util.concurrent.Executors
contains static factories, or use theThreadPoolExecutor
directly => to create thread pools.- fork-join pool => split task into smaller subtasks, steal jobs from one another => higher CPU utilization, higher throughput, lower latency.
- parallel streams are written atop fork-join pools.
- task: unit of work =>
- Choosing the executor service for a particular application can be tricky.
- small program, lightly loaded server =>
Executors.newCachedThreadPool
- heavily loaded production server =>
Executors.newFixedThreadPool
- small program, lightly loaded server =>
- Executor Framework => flexible interface-based task execution facility.
- Consequences
- Prefer concurrency utilities to
wait
andnotify
- Consequences
- Given the difficulty of using
wait
andnotify
correctly you should use the higher-level concurrency utilities instead.- e.g., the Executor Framework, concurrent collections, synchronizers.
- Concurrent collections manage their own synchronization
- => It is impossible to exclude concurrent activity from them.
- => Locking them will only slow the program.
- Given the difficulty of using
- Implementation
- Use
ConcurrentHashMap
in preference toCollections.synchronizedMap
. BlockingQueue
=> extendsQueue
, extended with blocking operations, used for work queues => producer-consumer pattern.- Synchronizers => enable threads to wait for one another and coordinate.
- e.g.,
CountDownLatch
,Semaphore
,CyclicBarrier
,Exchanger
,Phaser
.
- e.g.,
- For interval timing, always use
System.nanoTime
rather thanSystem.currentTimeMillis
. - Always use the wait loop idiom to invoke the
wait
method; never invoke it outside of a loop. - Always use
notifyAll
.
- Use
- Consequences
- Document thread safety
- Concequences
- The presence of the
synchronized
modifier in a method declaration is an implementation detail. - To enable safe concurrent use, a class must document what level of thread safety it supports.
- Immutable => no extra synchronization is necessary.
- Unconditionally thread-safe => sufficient internal synchronization, can be used concurrently without the need for extra synchronization.
- Conditionally thread-safe => some methods require external synchronization for safe concurrent use.
- => indicate which invocation sequences require external synchronization, and which lock must be acquired to execute these sequences.
- => use a private lock object in place of synchronized methods.
- Not thread-safe => must surround each method invocation with external synchronization.
- Thread-hostile => unsafe for any concurrent usage => usually fixed or deprecated.
- usually results from modifying static data without synchronization.
- thread safety annotations:
Immutable
,ThreadSafe
,NotThreadSafe
.
- The presence of the
- Concequences
- Use lazy initialization judiciously
- Motivation
- if it is costly to initialize a field and the field is accessed only on a fraction of the insatnces of a class => lazy initialization.
- lazy initialization: delaying the initialization of a field until its value is needed.
- Consequences
- The only way to know for sure whether lazy initialization is worthwhile is to measure the performance of the class with and without lazy initialization.
- Under most circumstances, normal initialization is preferable to lazy initialization.
- Implementation
- If you use lazy initialization to break an initialization circularity, use a synchronized accessor.
- If you use lazy initialization for performance on a static field, use the lazy initialization holder class idiom.
- => guarantees that a class will not be initialized until it is used.
- If you use lazy initialization for performance on an instance field, use the double-check idiom.
- => avoids the cost of locking when accessing the field after initialization.
- If you can tolerate repeated initialization, you can use single-check idiom.
- Motivation
- Don't depend on the thread scheduler
- Consequences
- Any program that relies on the thread scheduler for correctness or performance is likely to be nonportable.
- Threads should not run if they aren't doing useful work.
- => should not busy-wait, repeatedly checking a shared object waiting for its state to change.
- Thread priorities are among the least portable features of Java.
- Implementation
- Do not use
Thread.yield
to fix starved threads problem and it has no testable semantics.
- Do not use
- Consequences
- Prefer alternatives to Java serialization
- Motivation
- attack surface is too big to protect => all types in bytestream that can be serialized.
- e.g., deserialization bombs => cost a long time to deserialize.
- attack surface is too big to protect => all types in bytestream that can be serialized.
- Consequences
- The best way to avoid serialization exploits is never to deserialize anything.
- There is no reason to use Java serialization in any new system you write.
- => JSON => text-based, human-readable.
- => protobuf => binary, more efficient.
- Never deserialize untrusted data.
- If you can't avoid serialization, use the object deserialization filtering.
- Prefer whitelisting to blacklisting.
- Motivation
- Implement
Serializable
with great caution- Motivation
- implementing
Serializable
- => decreases the flexibility to change a class's implementation once it has been released.
- default serialized form => original byte-stream encoding => exported API.
- fail to declare a serial version UIDs => compatibility broken.
- => increases the likelihood of bugs and security holes.
- serialization => extralinguistic mechanism => open to invariant corruption and illegal access.
- => increase the testing burden associated with releasing a new version of a class.
- => decreases the flexibility to change a class's implementation once it has been released.
- implementing
- Consequences
- Implementing
Serializable
is not a decision to be undertaken lightly. - Classes designed for inheritance should rarely implement
Serializable
, and the interfaces should rarely extend it. - Inner classes should not implement
Serializable
.- A static member class can, however, implement
Serializable
.
- A static member class can, however, implement
- Implementing
- Motivation
- Consider using a custom serialized form
- Consequences
- Do not accept the default serialized form without first considering whetehr it is appropriate.
- The ideal serialized form contains only the logical data => independent of the physical representation.
- The default serialized form is likely to be appropriate if an object's physical representation is identical to its logical content.
- Even if you decide that the default serialized form is appropriate, you often must provide a
readObject
method to ensure invariants and security. - Using the default serialized form
- => It permanently ties the exported API to the current internal representation.
- => It can consume excessive space.
- => It can consume excessive time.
- => It can cause stack overflows.
- Do not accept the default serialized form without first considering whetehr it is appropriate.
- Implementation
@serial
tag => defines a public API, which is the serialized form of the class.transient
: a modifier indicating that an instance field is to be omitted from a class's default serialized form.- Make sure nontransient fields is part of the logical state of the object.
- The first thing
writeObject
does is to invokedefaultWriteObject
; the first thingreadObject
does is to invokedefaultReadObject
. - You must impose any synchronization on object serialization that you would impose on any other method that reads the entire state of the object.
- Declare an explicit serial version UID in every serializable class you write.
- Do not change the serial version UID unless you want to break compatibility with all existing serialized instances of a class.
- Consequences
- Write
readObject
methods defensively- Consequences
- When an object is deserialized, it is critical to defensively copy any field containing an object reference that a client must not possess.
- Like a constructor, a
readObject
method must not invoke an overridable method, either directly or indirectly.
- Implementation
- For classes with object reference fields that must remain private, defensively copy each object in such a field. Mutable components of immutable classes fall into this category.
- Check any invariants and throw an
InvalidObjectException
if a check fails. The checks should follow any defensive copying. - If an entire object graph must be validated after it is deserialized, use the
ObjectInputValidation
interface. - Do not invoke any overridable methods in the class, directly or indirectly.
- Consequences
- For instance control, prefer enum types to
readResolve
- Motivation
readResolve
=> allow you to substitute another instance for the one created byreadObject
.
- Consequences
- The accessibility of
readResolve
is significant.- on a final class => it should be private.
- on a nonfinal class => you must carefully consider its accessibility.
- Use enum types to enforce instance control invariants wherever possible,
- enless instances are not known at compile time.
- The accessibility of
- Implementation
- If you depend on
readResolve
for instance control, all instance fields with object reference types must be declaredtransient
.
- If you depend on
- Motivation
- Consider serialization proxies instead of serialized instances
- Implementation
- First, design a private static nested class that concisely represents the logical state of an instance of the enclosing class => serialization proxy.
- It should have a single constructor, whose parameter type is the enclosing class => merely copies the data from its argument.
- Both the enclosing class and its serialization proxy must be declared to implement
Serializable
.
- Next, add the
writeReplace
method to the enclosing class.- => emit a
SerializationProxy
instance to replace the instance of the enclosing class.
- => emit a
- Then, add
readObject
method to the enclosing class to prevent generating a serialized instance of the enclosing class. - Finally, provide a
readResolve
method on theSerializationProxy
class that returns a logically equivalent instance of the enclosing class.- => translate the serialization proxy back into an instance of the enclosing class upon deserialization.
- First, design a private static nested class that concisely represents the logical state of an instance of the enclosing class => serialization proxy.
- Implementation