54 返回空的数组或集合,不要返回null
像如下的方法并不罕⻅:
private final List<Cheese> cheesesInStock = ...; /** * @return a list containing all of the cheeses in the shop, * or null if no cheeses are available for purchase. */ public List<Cheese> getCheeses() { return cheesesInStock.isEmpty() ? null : new ArrayList<>(cheesesInStock); }
把没有奶酪(Cheese)可买的情况当做一种特例,这是不合常理的。这样需要在客户端中必须有额外的代码来处理null的返回值:
List<Cheese> cheeses = shop.getCheeses(); if (cheeses != null && cheeses.contains(Cheese.STILTON)) System.out.println("Jolly good, just the thing.");
这样做很容易出错,因为编写客户端的程序员可能忘记编写特殊情况代码来处理null返回。
下面是返回可能为空的集合的典型代码。一般情况下,这些都是必须的:
public List<Cheese> getCheeses() { return new ArrayList<>(cheesesInStock); }
如果有证据表明分配空集合会损害性能,可以通过重复返回相同的不可变空集合来避免多次分配
// Optimization - avoids allocating empty collections public List<Cheese> getCheeses() { return cheesesInStock.isEmpty() ? Collections.emptyList() : new ArrayList<>(cheesesInStock); }
数组的情况与集合的情况相同。永远不要返回null,而是返回⻓度为零的数组。
// Optimization - avoids allocating empty arrays private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0]; public Cheese[] getCheeses() { return cheesesInStock.toArray(EMPTY_CHEESE_ARRAY); }
55 谨慎返回optional
在Java 8之前,编写在特定情况下无法返回任何值的方法时,可以采用两种方法。要么抛出异常,要么返回null。这两种方式都不完美:
抛出异常代价很高,因为在创建异常时捕获整个堆栈trace
返回null有可能抛NullPointerException异常
在Java 8中,还有第三种方法来编写可能无法返回任何值的方法。Optional<T>类表示一个不可变的容器,它可以包含一个非null的T引用,也可以什么都不包含。
不包含任何内容的Optional被称为空(empty)。非空的包含值称的Optional被称为存在(present)
返回Optional的方法比抛出异常的方法更灵活、更容易使用,而且比返回null的方法更不容易出错。
例如在第30条中,有一个根据集合中元素的自然顺序计算集合最大值的方法:
public static <E extends Comparable<E>> E max(Collection<E> c) { if (c.isEmpty()) throw new IllegalArgumentException("Empty collection"); E result = null; for (E e : c) if (result == null || e.compareTo(result) > 0) result = Objects.requireNonNull(e); return result; }
如果给定集合为空,此方法将抛出IllegalArgumentException
异常。更好的替代方法是返回Optional<E>
。下面是修改后的方法:
public static <E extends Comparable<E>> Optional<E> max(Collection<E> c) { if (c.isEmpty()) return Optional.empty(); E result = null; for (E e : c) if (result == null || e.compareTo(result) > 0) result = Objects.requireNonNull(e); return Optional.of(result); }
将null传递给Optional.of(value)是一个编程错误,会抛NullPointerException异常。Optional.ofNullable(value)方法接受一个可能为null的值,如果传入null则返回一个空的Optional。
Stream 上的很多终止操作返回Optional。可以用Stream重写max方法,Stream的max 操作会为我们生成Optional的工作:
public static <E extends Comparable<E>> Optional<E> max(Collection<E> c) { return c.stream().max(Comparator.naturalOrder()); }
如果方法返回一个Optional,则客户端可以选择在方法无法返回值时要采取的操作。有以下两种方式:
1. 指定默认值
String lastWordInLexicon = max(words).orElse("No words...");
2. 抛出任何适当的异常
Toy myToy = max(toys).orElseThrow(TemperTantrumException::new);
有时候,可能会遇到这样一种情况:获取默认值的代价很高,我们希望避免这种代价。对于这些情况,Optional提供了一个方法orElseGet,传入一个Supplier<T>。
Optional还提供了isPresent()方法,可以将其视为安全阀。如果Optional包含值,则返回true;否则返回false。
当使用Stream时,经常会遇到Stream<Optional<T>>,为了推动进程还需要一个包含了非空optional中所有元素的Stream<T>。Java 8里可以这样写:
streamOfOptionals .filter(Optional::isPresent) .map(Optional::get)
并不是所有的返回类型都能从Optional的处理中获益。容器类型,包括集合、映射、Stream、数组和Optional,不应该封装在Optional中。与其返回一个空的Optional<List<T>>,不还如返回一个空的List<T>。
那么什么时候应该声明一个方法来返回Optional<T> 而不是T 呢?
如果可能无法返回结果,并且在没有返回结果,客户端还必须执行特殊处理的情况下,则应声明返回Optional <T>的方法。
使用Optional还有一些其他的注意事项:
永远不应该返回基本包装类型的Optional(小型的Boolean,Byte,Character,Short 和 Float 除外)
不适合将optional作为键、值、集合或数组中的元素
除了作为返回值之外,不要在任何其他地方中使用Optional
56 为所有已公开的API 元素编写文档注释
如果API要可用,就必须对其编写文档化。
要正确地记录API,必须在每个导出的类、接口、构造方法、方法和属性声明之前加上文档注释。如果一个类是可序列化的,应该对它的序列化形式编写文档。puiblic类不应该使用无参构造方法,因为无法为它们提供文档注释。要编写可维护的代码,还应该为所有没有被导出的类、接口、构造方法、方法和属性编写文档注释,尽管这些注释不需要像导出API元素那样完整。
方法的文档注释应该简洁地描述方法与其客户端之间的约定。这个约定应该说明方法做了什么,而不是它如何完成工作的。文档注释应该列举方法的所有前置条件以及后置条件
前置条件:为了使客户能够调用这个方法,必须要满足的条件
后置条件:调用完成之后,哪些条件必须满足
通常,每个未受检的异常都对应一个前提违例(precondition violation),要在受影响的参数的 @param 标签中指定前置条件
方法还应在文档中记录它的副作用(side effort)。例如,如果方法启动后台线程,则应该在文档里说明。
文档注释应该为每个参数都有一个 @param 标签,一个 @return 标签,以及一个 @throw 标签。
@param 或 @return 标签后面的文本应该是一个名词短语,描述参数或返回值所表示的值。@throw 标签后面的文本应该包含单词「if」。@param 、@return 或 @throw 标签后面的短语或子句都不用句号来结束。
/** * 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);
在此文档注释中使用了HTML标记(<p>和<i>)。Javadoc实用工具将文档注释转换为HTML。
@throw子句中的代码片段周围使用Javadoc的{@code}标签。它使代码片段以 code font(代码字体)形式呈现
@implSpec 注释描述了方法与其子类之间的约定,如果子类继承了该方法,或者通过super调用了方法,则允许子类依赖实现行为。
/** * 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() { ... }
包含HTML元字符的文档,例如小于号(<),大于号(>)和 and 符号(&),是用{@literal}
标签将它们包围起来:
* A geometric series converges if {@literal |r| < 1}.
文档注释在源代码和生成的文档中都应该是易读的。
每个文档注释的第一个「句子」是注释所在元素的概要描述。同一个类或接口中的两个成员或构造方法不应具有相同的概要描述。
注意概要描述是否包含句点,例如以「A college degree, such as B.S., M.S. or Ph.D.」会导致概要描述为「A college degree, such as B.S., M.S」。缩写「M.S.」中的第二个句号后面跟着一个空格。最好的解决方案是用{@literal}标签
/** * A college degree, such as B.S., {@literal M.S.} or Ph.D. */ public class Degree { ... }
概要描述应该是一个动词短语,描述了该方法执行的操作。例如:
ArrayList(int initialCapacity) ——构造具有指定初始容量的空列表。
Collection.size() ——返回此集合中的元素个数。
对于类,接口和属性,概要描述应该是描述由类或接口的实例或属性本身表示的事物的名词短语。
Instant ——时间线上的瞬时点。
Math.PI ——更加接近pi的double类型数值,即圆的周⻓与其直径之比。
为泛型类型或方法写文档时,请务必记录所有类型参数:
/** * 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> { ... }
在记录枚举类型时,一定要记录常量:
/** * An instrument section of a symphony orchestra. */ public enum OrchestraSection { /** Woodwinds, such as flute, clarinet, and oboe. */ WOODWIND, /** Brass instruments, such as french horn and trumpet. */ BRASS, /** Percussion instruments, such as timpani and cymbals. */ PERCUSSION, /** Stringed instruments, such as violin and cello. */ STRING; }
在为注解类型记录文档时,一定要记录任何成员:
/** * Indicates that the annotated method is a test method that * must throw the designated exception to pass. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface ExceptionTest { /** * The exception that the annotated test method must throw * in order to pass. (The test is permitted to throw any * subtype of the type described by this class object.) */ Class<? extends Throwable> value(); }
无论类或静态方法是否线程安全,都应该在文档中描述其线程安全级别
Javadoc具有「继承(inherit)」方法注释的能力。如果API元素没有文档注释,Javadoc将搜索最适用的文档注释,接口文档注释优先于超类文档注释。
对于由多个相互关联的类组成的复杂API,通常需要用描述API总体架构的外部文档来补充文档注释。如
果存在这样的文档,相关的类或包文档注释应该包含到外部文档的链接。