方法的参数限制, 应该在文档中指明, 并且在方法体的开头处检查参数, 以强制施加这些限制.
对于公有的方法, 要用Javadoc的@throws
标签在文档中说明违反参数值限制时会抛出的异常.
Java 7新增了方法Objects.requireNonNull
, 很好用.
Java 9新增了checkFromIndexSize
, checkFromToIndex
, 和checkIndex
.
非公有的方法通常应该使用断言(assertion)来检查它们的参数. 如果断言失败会抛出AssertionError, 如果它们没有起到作用, 本质上也不会有成本开销. (除非通过将-ea或者-enableassertions标记传递给Java解释器来启用它们.)
你的类是否能够容忍对象进入数据结构之后发生变化? 如果答案是否定的, 就必须对该对象进行保护性拷贝, 并且让拷贝之后的对象而不是原始对象进入到数据结构中.
在内部组件被返回给客户端之前, 对它们进行保护性拷贝也是同样的道理.
如果参数的类型是可以被不被信任的人子类化的, 那么对参数进行保护性拷贝的时候, 不要使用clone
方法.
只要有可能, 都应该使用不可变的对象作为对象内部的组件, 这样就不必再为保护性拷贝操心.
如果拷贝的成本受到限制, 并且类信任它的客户端不会不恰当地修改组件, 就可以在文档中指明客户端的职责是不得修改受到影响的组件, 以此来代替保护性拷贝.
API设计技巧:
- 谨慎选择方法名.
- 不要过于追求提供便利的方法.
- 避免过长的参数列表. -> 1.分解成多个方法; 2.创建辅助类, 用来保存参数的分组; 3.从对象构建到方法调用都采用Builder模式.
- 参数类型优先使用接口而不是类.
- 对于boolean参数, 要优先使用两个元素的枚举类型.
对于重载(overload)方法的选择是静态的, 而对于被覆盖(override)方法的选择则是动态的.
选择被覆盖方法的正确版本是在运行时进行的, 选择的依据是被调用方法所在对象的运行时类型. 所以子类方法与基类签名相同, 则覆盖基类, 尽管对象声明为基类, 但是调用时用的是子类的实现.
但重载的选择工作是在编译时进行的, 完全基于参数的编译时类型. 这样的代码很容易使人感到困惑.
安全而保守的策略是: 永远不要导出两个具有相同参数数目的重载方法. 如果方法使用可变参数(varargs), 保守的策略是不要重载它.
这项限制并不麻烦, 因为你始终可以给方法起不同的名称而不使用重载机制.
对于构造器, 没有选择不同名称的机会, 在许多情况下, 可以选择导出静态工厂.
当然如果对于每一种重载方法, 至少有一个对应的参数在两个重载方法中具有根本不同的类型, 就不会产生迷惑. 在这种情况下主要的混淆根源就消除了.
注意泛型和自动装箱会引起的歧义性, 举例 -> List<E>
的remove(E)
和remove(int)
.
Java 8引入的lambda和方法引用更增加了可能会引起重载解析歧义的可能性. -> 重载方法中, 不要在同样的参数位置接受不同的函数式接口.
可变参数机制通过先创建一个数组, 数组的大小为在调用位置所传递的参数数量, 然后将参数传到数组中, 最后将数组传递给方法.
在重视性能的情况下, 使用可变参数机制要特别小心.
在定义参数数目不定的方法时, 可变参数是一种很方便的方式, 但是它们不应该被过度滥用.
返回类型为数组或集合的方法, 应该返回一个零长度的数组或者集合, 没理由返回null. -> 不好用, 容易出错, 没有性能优势.
开销考虑:
- 在这个级别上担心性能问题是不明智的, 除非分析表明这个方法是造成性能问题的真正源头.
- 对于不返回任何元素的调用, 每次都返回同一个零长度数组是有可能的. (例如:
Collections.emtpySet
).
在Java 8之前, 当一个方法无法返回值的时候有两种选择: 返回null或者抛出异常.
Java 8推出了一个新的解决方案: Optional<T>
: 不可变容器, 含有一个或0个值.
Optional的精神和checked exception一样, 强迫用户意识到返回值有可能是为空的. 例子:
// 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();
如果默认值的计算比较重, 可以用orElseGet
方法.
还有很多方法供各种用途: filter
, map
, flatMap
, ifPresent
等.
如果各种方法都不能满足你的需要, isPresent
方法是一个很方便的方法, 它有值返回true, 无值返回false, 可以用来实现各种需求.
(不过通常可以用上面的各种方法更加优雅地解决问题.)
也不是所有的类型都可以从Optional受益, 容器类型(collections, maps, streams, arrays)和optionals不应该再用Optional包装.
Optional也不适用于性能关键的情形.
不要用Optional包装基本类型的装箱类型.
对基本类型的装箱类型返回Optional是有性能开销的, 还不如直接返回基本类型的默认值0.
库提供了OptionalInt
, OptionalLong
和OptionalDouble
.
通常, 用optional作为key, value或者集合中的元素都是不合适的, 会造成不必要的复杂性. 把optional保存在字段中也通常是一个bad smell. 但是也有例外, 比如想要合理地表达absence.
Javadoc可以根据源代码自动生成API文档.
要正确地为API建立文档, 就必须在每个导出的类, 接口, 构造函数, 方法和字段声明之前加上doc注释.
方法的文档注释应该简洁地描述出它和客户端之间的约定. 这个约定应该说明这个方法做了什么, 而不是如何完成这项工作的.
方法的文档注释还应该列举出:
- 所有前提条件. 一般可以利用
@throws
,@param
. - 后置条件.
- 副作用.
- 线程安全性.
- 每个参数:
@param
名词短语. - 返回值:
@return
名词短语. (除非和方法描述一致时, 可根据所遵循的规定省略.) - 每个异常:
@throws
含if的名词短语.
按惯例, @param
, @return
, @throws
后面的短语或句子都不用句点来结束.
{@code}
用来标记代码, 多行代码要加上<pre>
标签, 变成: <pre>{@code xxx}</pre>
.
注意代码中的注解符号@需要被省略.
按照惯例, 方法的文档注释中的"this"指代的是当前的对象.
Java 8新增@implSpec
: 描述方法和子类的关系.
Java 9中Javadoc utility会忽略@implSpec
, 除非你在命令行加上"implSpec:a:Implementation Requirements:"
如果文档中要用HTML中的元素, 比如<
, >
和&
, 需要加上{@literal}
标签.
文档注释必须在代码和生成文档中都保证可读性, 如果不能两者都保证, 生成文档的可读性优先.
每个文档注释的第一句话成了该注释所属元素的概要描述(summary description). 为了避免混乱, 在类或者接口中不应该有两个成员或者构造方法有相同的概要描述. 尤其要注意方法重载.
对于方法和构造器而言, 概要描述应该是个完整的动词短语, 它描述了该方法所执行的动作. 对于类, 接口和域, 概要描述应该是一个名词短语.
Java 9引入了index, 方面文档查询. 偶尔你需要用{@index}
加入额外的index.
泛型, 枚举, 注解都需要额外的注意:
- 当为泛型方法写文档时, 需要为每个泛型参数写文档注释.
- 枚举需要为每个常量写注释.
- 注解需要注释每个成员. (注解的概要描述是个动词短语.)
包级文档注释: package-info.java
.
模块级文档注释: module-info.java
.
在文档中还应该标明:
- 线程安全性 -> 不论是否线程安全.
- 如果可序列化, 需标明序列化形式.
Javadoc可以继承方法注释.
你可以用{@inheritDoc}
标签来继承部分文档注释. (tricky and has some limitations).