The following post is a resumé of the best practices I understand when reading “Effective Java 3” of Joshua BLOCH.
Item 49 : Check parameters for validity
You should clearly document all method parameter restriction and enforce them with checks at the beginning of the method body. If an invalid parameter value is passed to the method and the method checks its parameters before execution, it will fail quickly and cleanly with an appropriate exception.
For public and protected methods, use the Javadoc @throws
tag to document the exception that will be thrown, if a restriction o n a parameter values is violated.
/**
*
*
* @param numberOfPersons number of person
* @throws IllegalArgumentException if numberOfPersons < 2
*/
void compute(int numberOfPersons) {
if (numberOfPersons < 2) {
throw new IllegalArgumentException("Number of Persons must be >= 2") ;
}
// Do some stuffs
}
The Objects.requireNonNull
method added in Java 7, is flexible and convenient, so there’s no reason to perform null
checks manually anymore.
// Inline use of Java's null-checking facility
Objects.requireNonNull(elementsCount, "Cannot support null elements count") ;
If you invoke this method with null parameters
Exception in thread "main" java.lang.NullPointerException: Cannot support null elements count
at java.base/java.util.Objects.requireNonNull(Objects.java:246)
Non public methods can check their parameters using assertions
, as shown below
static private void sort(long[] data, int offset) {
assert data != null ;
assert offset< data.length ;
}
Assertions throw AssertionError
if they fail, and have no effects and essentially no cost unless you enable them by passing the -ea
or -enableassertions
flag to the java command.
Exception in thread "main" java.lang.AssertionError
at part8.Item49.sort(Item49.java:32)
at part8.Item49.main(Item49.java:10)
It is critical to check the validity of constructor parameters to prevent the construction of an object that violates its class invariants.
Occasionally, a computation implicitly performs a required check validity check, but throws the wrong exception if the check fails. You should use exception translation idiom to translate the natural exception into the correct one.
try {
File.createTempFile("prefix","suffix") ;
} catch (IOException e) {
throw new CannotCreateFileException(e) ;
}
You should design methods to be as general as it is practical to make them. The fewer restriction that you place on parameters, the better, assuming the method can do something reasonable with all of the parameter values that if accepts.
Item 50 : Make defensive copies when needed
Java is a safe language : It is possible to write classes and know with certainty that their invariants will hold, no matter what happens in nay other part of the system.
Even in a safe language, you must program defensively, with the assumption that clients of you class will do their best to destroy its invariants.
Let’s take the following example :
class Period{
private final Date start ;
private final Date end ;
public Period(Date start, Date end) {
this.start = start;
this.end = end;
}
public Date getStart() {
return start;
}
public Date getEnd() {
return end;
}
}
First attack
This class may appear immutable, but, it is easy to violate this invariants :
Date start = new Date() ;
Date end = new Date() ;
Period period = new Period(start,end);
start.setTime(123123123L); // modifies internal of period
To protect the internals of a Period
from this sort of attacks, it is essential to make defensive copies of each mutable parameter in the constructor. Note that defensive copies should be made before checking the validity of the parameters. It protects the class against changes to the parameters from another thread during the window of vulnerability between the parameters are checked and the time they are copied.
public Period(Date start, Date end) {
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
}
Do not use the clone
method to make defensive copy of parameter whose type is subclassed by untrusted parties.
Second attack
It is possible to change internals of Period
class using getter
:
Period period = new Period(start,end);
period.getEnd().setTime(123123L);
To defend against the second attack, merely modify the accessor to return defensive copies of mutable internal fields :
public Date getStart() {
return new Date(end.getTime());
}
public Date getEnd() {
return new Date(end.getTime());
}
In the accessor, it whould be permissible to use clone
method to make defensive copy.
Solution Instant
Date
is obsolete and should no longer be used in new code. The obvious way to fix that is to use Instant
.
public static void main(String[] args) {
Instant start = Instant.now();
Instant end = Instant.now() ;
Period period = new Period(start,end);
start.plus(123, ChronoUnit.HOURS) ; // Returns a copy of this instant with the specified amount added.
}
static class Period{
private final Instant start ;
private final Instant end ;
public Period(Instant start, Instant end) {
this.start = start ;
this.end = end ;
}
public Instant getStart() {
return start;
}
public Instant getEnd() {
return end;
}
}
Perfs ?
There may be a performance penalty associated with defensive copying and it sin’t always justified. If a class trust a classer not to modify an internal component, then it may be appropriate to dispose with defensive copying.
Item 51 : Design (API) method signatures carefully
- Choose method name carefully (understandable and consistent)
- Don’t go overboard in providing convenience method (For each action supported by your class, provide a fully functional method).
- Avoid long parameters list (max 4 parameters). There 3 techniques for shortening overly long parameters list
- Break method into multiple methods
- Create helper class to hold group of parameters (typically
static
member classes) - Adapt the Builder pattern (Create an object with all the parameters, with setters and
execute
method which does the final validity check on the parameters and perform the actual computation)
- Favor interfaces over classes for parameter types
- Prefer 2-element
enum
type rather thanboolean
.
Item 52 : Use overloading judiciously
It is important to know that
- the choice of which overloading to invoke is made at compile time.
- the selection among overloaded methods is static, while selection among overridden methods is dynamic
private static String describe(List<?> list) {
return "list" ;
}
private static String describe(Collection<?> collect) {
return "Collection : " +collect.getClass().toString() ;
}
public static void main(String[] args) {
Collection<?>[] collection = {Lists.newArrayList(), Sets.newHashSet()} ;
Arrays.stream(collection).map(Item52::describe).forEach(System.out::println);
}
The following code prints :
Collection : class java.util.ArrayList
Collection : class java.util.HashSet
You should avoid confusing use of overloading. A safe, conservative policy is never to export two overloading with the same number of parameters.
For constructors, you don’t have the option of using different names. You do have in many cases the option of exporting static factories instead of constructors.
Item 53 : Use varargs judiciously
Varargs methods, formally known as variable arity methods accept zero or more arguments of a specific type.
static int sum(int... values) {
return Arrays.stream(values).sum() ;
}
public static void main(String[] args) {
System.out.println(sum());
System.out.println(sum(1,2));
}
A wrong way to use varargs is when at least one argument is excepted :
// WRONG way
static int sum(int... values) {
if (values.length == 0) {
throw new IllegalArgumentException("Specify at least one element !")
}
int weight = values[0] ;
return weight * Arrays.stream(values).sum() ;
}
The right way in this case is
static int sum(int weight, int... values) {
return weight * Arrays.stream(values).sum() ;
}
Exercise care when using varargs in performance critical situations. Every invocation of a varargs method causes an array allocation and initialization.
Item 54 : Return empty collections or arrays not nulls
It is not uncommon to see methods return null
:
static List<String> toList(String... args) {
if (args.length == 0) {
return null ;
}
return Lists.newArrayList("...") ;
}
Doing so requires extra code in client side :
List<String> datas = toList() ;
if (datas!= null && datas.contains("AZE")) {
// Do some stuff ...
}
It is error-prone because the programmer writing the client code might forget to write the special case code to handle a null
return.
It is sometimes argued that a null
return value is preferable to an empty collection or array, but this argument fails :
- It is inadvisable to worry about performance at this level
- It is possible to return empty collection of table without allocating them
static List<String> toList(String... args) {
return Lists.newArrayList(args) ;
}
// Using Collection.emptyList()
static List<String> toList(String... args) {
return args.length == 0 ? Collections.emptyList() : Lists.newArrayList(args) ;
}
The situation for arrays is identical to that for collection :
// Optimization - avoids allocating empty arrays
private static final String[] EMPTY_DATA = new String[0] ;
static String[] getData() {
return data.toArray(EMPTY_DATA) ;
}
Do not preallocate the array passed to toArray
in hope of improving performances Studies shows that it is contreproductive :
// DO NOT DO THIS !!
data.toArray(new String[data.size()]) ;
Item 55 : Return Optional
judiciously
In Java 8, there is an approach to writing method that may not be able to return a value : Optional<T>
class represents an immutable container that can hold either a single non-null T
reference or nothing at all.
Let’s take a look at this method
static <E extends Comparable<E>> E max(Collection<E> collection) {
if (collection.isEmpty()) {
throw new IllegalArgumentException("Cannot support Empty collection !") ;
}
E max = null ;
for (E e : collection) {
if (max == null || e.compareTo(max) > 0) {
max = e ;
}
}
return max ;
}
With Optional
, the method looks like
static <E extends Comparable<E>> Optional<E> max(Collection<E> collection) {
if (collection.isEmpty()) {
return Optional.empty() ;
}
E max = null ;
for (E e : collection) {
if (max == null || e.compareTo(max) > 0) {
max = e ;
}
}
return Optional.of(max) ;
}
Using terminal operator max
which returns an Optional
:
static <E extends Comparable<E>> Optional<E> max(Collection<E> collection) {
return collection.stream().max(Comparator.naturalOrder()) ;
}
Never return
null
value from anOptional
returning method. It defeats the entire purpose of the facility.
If a method returns an optional, the client gets to choose what action to take if the method can’t return a value :
// Provide default value
max = max(words).orElse("No words ... ") ;
// Throw a choosen exception
max = max(words).orElseThrow(CannotFindAnyBookException::new) ;
Let’s take the following example :
Optional<Process> data = getProcess() ;
System.out.println(data.isPresent() ? data.get().getId() : "NA");
This code can be replaced with :
System.out.println(getProcess().map(Process::getId).orElse("NA")) ;
Container types, including collections, maps, arrays and optionals should not be wrapped in optionals. You should never return an optional of a boxed primitive type. Use
OptionalInt
,OptionalLong
andOptionalDouble
. It is almost never appropriate to use an optional as a key, value, or element in collection or array
Item 56 : Write doc comments for all exposed API elements
If an API is to be usable, ti must be documented. The java programming environment generates API documentation automatically from source code with specially formatted documentation comments, more commonly known as doc comments.
To document your API properly, you must precede every exported class, interface, constructor, method, and field with a doc comment. It is frustrating and error-prone to use an API with missing documentation comments.
Documentation
- The doc comment for a method should describe the contract between the method and its client :
- what the method does ?
- how it does its job ?
- preconditions (
@throws
) - side effects
- Doc comments should be readable both in the source code and in the generated documentation.
- When documenting a generic type or method, be sure to document all type parameters
- When documenting an
enum
type, be sure to document the constants - When documenting an annotation type, be sure to document any members as well as the type itself
- Whether or not a class or static method os thread-safe, you should document it thread-safety
Javadoc automatically checks for adherence to many of the recommendations in this item. In Java 7, the command line switch -Xdoclint
was required to get this behavior. In java 8 & 9, checking is enabled by default.