Methods

Item49: Check parameters for validity

We should document what restrictions for the input parameters of methods, and try to detect these kinds of error as early as possible.

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.

The Objects.requireNonNull method, added in Java 7, is flexible and convenient, so there's no reason to perform null checks manually anymore.

this.strategy = Objects.requireNonNull(strategy, "strategy");

There are also some annotations before single parameters in the parameter list of methods, like:

public void method(@NotNull Object obj1);

Nonpublic methods can check their parameters using assertions, as shown below:

private static void sort(long a[], int offset, int length) {
  assert a != null;
  assert offset >= 0 && offset <= a.length;
  assert length >= 0 && length <= a.length - offset;
  ... // Do the computation
}

Unlike normal validity checks, they have no effect and essentially no cost unless you enable them, which you do by passing the -ea (or -enableassertions) flag to the java command.

It is particularly important to check the validity of parameters that are not used by a method, but stored for later use. Because error caused by this kind of parameters are harder to detect and debug. Constructors represent a special case of the principle that you should check the validity of parameters that are to be stored away for later use.

An important exception is the case in which the validity check would be expensive or impractical and the check is performed implicitly in the process of doing the computation.

We should design methods to be as general as it is practical to make them. The fewer restrictions that you place on parameters, the better,

Item50: Make defensive copies when needed

You must program defensively, with the assumption that clients of your class will do their best to destroy its invariants. Though a class is marked as immutable, sometimes operations on its references would destroy this guarantee. For example, if we pass an object reference in the constructor, and store the reference directly in an instance field, then the external modification of the object would have influence on the reference inside the class instance. In this way, it is essential to make a defensive copy of each mutable parameter to the constructor.

public Period(Date start, Date end) {
  this.start = new Date(start.getTime());
  this.end = new Date(end.getTime());
  
  if (this.start.compareTo(this.end) > 0)
    throw new IllegalArgumentException(this.start + " after " + this.end);
}

Note that defensive copies are made before checking the validity of the parameters (Item 49), and the validity check is performed on the copies rather than on the originals. It protects the class against changes to the parameters from another thread during the window of vulnerability between the time the parameters are checked and the time they are copied.

Attention: do not use the clone method to make a defensive copy of a parameter whose type is subclassable by untrusted parties.

Also, we should do something for accessors:

public Date start() {
	return new Date(start.getTime());
}

public Date end() {
	return new Date(end.getTime());
}

This is used to protect our code from attack such as getting the internal instances and modifying them.

Defensive copying of parameters is not just for immutable classes. Any time you write a method or constructor that stores a reference to a client-provided object in an internal data structure, think about whether the client-provided object is potentially mutable. The same is true for defensive copying of internal components prior to returning them to clients. Whether or not your class is immutable, you should think twice before returning a reference to an internal component that is mutable. If the cost of the copy would be prohibitive and the class trusts its clients not to modify the components inappropriately, then the defensive copy may be replaced by documentation outlining the client's responsibility not to modify the affected components.

Item51: Design method signatures carefully

This item is a grab bag of API design hints to help make your API easier to learn and use and less prone to errors.

  • Choose method names carefully.
  • Don't go overboard in providing convenience methods. When in doubt, leave it out.
  • Avoid long parameter lists. Long sequences of identically typed parameters are especially harmful.
    • break the method up into multiple methods
    • create helper classes to hold groups of parameters
    • combine aspects of the first two is to adapt the Builder pattern (Item 2) from object construction to method invocation
  • For parameter types, favor interfaces over classes.
  • Prefer two-element enum types to boolean parameters.

Item52: Use overloading judiciously

public class CollectionClassifier {
  public static String classify(Set<?> s) {
  	return "Set";
  }
  public static String classify(List<?> lst) {
  	return "List";
  }
  public static String classify(Collection<?> c) {
  	return "Unknown Collection";
  }
  
  public static void main(String[] args) {
    Collection<?>[] collections = {
      new HashSet<String>(),
      new ArrayList<BigInteger>(),
      new HashMap<String, String>().values()
    };
    
    for (Collection<?> c : collections)
    	System.out.println(classify(c));
  }
}

We may expect the print is:

Set
List
Unknown Collection

But the result truly is:

Unknown Collection
Unknown Collection
Unknown Collection

Why? Because the classify method is overloaded, and the choice of which overloading to invoke is made at compile time. The compile-time type of the parameter is Collection<?>, the only applicable overloading is the third one, which is invoked in each iteration of the loop.

Remember: Selection among overloaded methods is static, while selection among overridden methods is dynamic. So we should use overriden methods and they would act correctly at runtime.

Or, we can do like this:

public static String classify(Collection<?> c) {
  return c instanceof Set ? "Set" : c instanceof List ? "List" : "Unknown Collection";
}

If we do need to use overloading, a safe, conservative policy is never to export two overloadings with the same number of parameters. In fact, you can always give methods different names instead of overloading them.

Sometimes, combined with auto-boxing, overloading can be even more confusing: Imagine we have two overloading methods, one accepts an Integer parameter to indicate which element should be removed, and the other accepts an int parameter as the index of the element to be removed. Then we could imagine what chaos would be leaded to with the use of int and Integer.

Also, overloading methods or constructors with different functional interfaces in the same argument position causes confusion. Therefore, do not overload methods to take different functional interfaces in the same argument position.

To summarize, just because you can overload methods doesn't mean you should. It is generally best to refrain from overloading methods with multiple signatures that have the same number of parameters.

Item53: Use varargs judiciously

Varargs methods, formally known as variable arity methods, accept zero or more arguments of a specified type. The varargs facility works by first creating an array whose size is the number of arguments passed at the call site, then putting the argument values into the array, and finally passing the array to the method.

static int sum(int... args) {
  int sum = 0;
  for (int arg : args)
  	sum += arg;
  return sum;
}

So how to write a method that accepts one or more arguments of some type?

static int min(int firstArg, int... remainingArgs) {
  int min = firstArg;
  for (int arg : remainingArgs)
    if (arg < min)
    	min = arg;
  return min;
}

But the cost of creating an array is real. To deal with it, we can create several overloadings of the method with different numbers of arguments, but this is only applicable for the methods with most conditions that there are only a few arguments needed.

public void foo() { }
public void foo(int a1) { }
public void foo(int a1, int a2) { }
public void foo(int a1, int a2, int a3) { } 
// the above method invocations could cover 95% total invocations

public void foo(int a1, int a2, int a3, int... rest) { }

Item54: Return empty collections or arrays, not nulls

If we return null from a method, then users of the method would have to check the returned value, which is redundant. Therefore, we should return an empty collection or array, instead of a null value.

public List<Cheese> getCheeses() {
	return new ArrayList<>(cheesesInStock);
}

To optimize the code, we could avoid creating and returning an empty collection each time:

// Optimization - avoids allocating empty collections
public List<Cheese> getCheeses() {
  return cheesesInStock.isEmpty() ? Collections.emptyList() : new ArrayList<>(cheesesInStock);
}

For array:

public Cheese[] getCheeses() {
	return cheesesInStock.toArray(new Cheese[0]);
}

We're passing a zero-length array into the toArray method to indicate the desired return type, which is Cheese[].

It can be also optimized:

// Optimization - avoids allocating empty arrays
private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0];

public Cheese[] getCheeses() {
	return cheesesInStock.toArray(EMPTY_CHEESE_ARRAY);
}

In the optimized version, we pass the same empty array into every toArray call, and this array will be returned from getCheeses whenever cheesesInStock is empty.

Item55: Return optionals judiciously

An approach to writing methods that may not be able to return a value: The Optional<T> class represents an immutable container that can hold either a single non-null T reference or nothing at all. An Optional-returning method is more flexible and easier to use than one that throws an exception, and it is less error-prone than one that returns null. And, never return a null value from an Optional-returning method: it defeats the entire purpose of the facility.

Many terminal operations on streams return optionals.

public static <E extends Comparable<E>> Optional<E> max(Collection<E> c) {
  return c.stream().max(Comparator.naturalOrder());
}

So how do you choose to return an optional instead of returning a null or throwing an exception? Optionals are similar in spirit to checked exceptions (Item 71), in that they force the user of an API to confront the fact that there may be no value returned.

If a method returns an optional, the client gets to choose what action to take if the method can't return a value.

// Using an optional to provide a chosen default value
String lastWordInLexicon = max(words).orElse("No words...");

// Using an optional to throw a chosen exception
Toy myToy = max(toys).orElseThrow(TemperTantrumException::new);

// Using optional when you know there's a return value
Element lastNobleGas = max(Elements.NOBLE_GASES).get();

We can also run some more complicated codes with ifPresentOrElse (since Java 9), filter, map, flatMap and ifPresent.

System.out.println("Parent PID: " + ph.parent().map(h -> String.valueOf(h.pid())).orElse("N/A"));

Not all return types benefit from the optional treatment. Container types, including collections, maps, streams, arrays, and optionals should not be wrapped in optionals. Empty collections or arrays are just enough.

As a rule, you should declare a method to return Optional<T> if it might not be able to return a result and clients will have to perform special processing if no result is returned.

Returning an optional that contains a boxed primitive type is prohibitively expensive compared to returning a primitive type because the optional has two levels of boxing instead of zero. Therefore, we should never return an optional of a boxed primitive type.

Also, it is almost never appropriate to use an optional as a key, value, or element in a collection or array. What's more, we should be careful when storing optionals in fields, which is generally a bad idea but sometimes useful.

Item 56: Write doc comments for all exposed API elements

If an API is to be usable, it must be documented. Javadoc 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 declaration with a doc comment. To write maintainable code, you should also write doc comments for most unexported classes, interfaces, constructors, methods, and fields.

The doc comment for a method should describe succinctly the contract between the method and its client. The contract should say what the method does rather than how it does its job.

In addition to preconditions and postconditions, methods should document any side effects.

To describe a method's contract fully, the doc comment should have an @param tag for every parameter, an @return tag unless the method has a void return type, and an @throws tag for every exception thrown by the method, whether checked or unchecked.

/**
* Returns the element at the specified position in this list.
*
* <p>This method is <i>not</i> guaranteed to run in constant
* time. In some implementations it may run in time proportional
* to the element position.
*
* @param index index of element to return; must be
* non-negative and less than the size of this list
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException if the index is out of range
* ({@code index < 0 || index >= this.size()})
*/
E get(int index);

When you design a class for inheritance, you must document its self-use patterns, so programmers know the semantics of overriding its methods. These self-use patterns should be documented using the @implSpec tag.

/**
 * Returns true if this collection is empty.
 *
 * @implSpec
 * This implementation returns {@code this.size() == 0}.
 *
 * @return true if this collection is empty
 */
public boolean isEmpty() { ... }

To avoid confusion, no two members or constructors in a class or interface should have the same summary description.

Generics, enums, and annotations require special care in doc comments. When documenting a generic type or method, be sure to document all type parameters:

/**
 * An object that maps keys to values. A map cannot contain
 * duplicate keys; each key can map to at most one value.
 *
 * (Remainder omitted)
 *
* @param <K> the type of keys maintained by this map
 * @param <V> the type of mapped values
 */
public interface Map<K, V> { ... }

When documenting an enum type, be sure to document the constants as well as the type and any public methods.

When documenting an annotation type, be sure to document any members as well as the type itself.

Two aspects of APIs that are often neglected in documentation are threadsafety and serializability. Whether or not a class or static method is threadsafe, you should document its thread-safety level.