Effective Java 第三版——49. 检查参数有效性

Tips
《Effective Java, Third Edition》一书英文版已经出版,这本书的第二版想必不少人都读过,号称Java四大名著之一,不过第二版2009年出版,到如今已经将近8年的时间,但随着Java 6,7,8,甚至9的发布,Java语言发生了深入的变化。
在这里第一时间翻译成中文版。供你们学习分享之用。
书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code
注意,书中的有些代码里方法是基于Java 9 API中的,因此JDK 最好下载 JDK 9以上的版本。可是Java 9 只是一个过渡版本,因此建议安装JDK 10。html

Effective Java, Third Edition

49.检查参数有效性

本章(第8章)讨论了方法设计的几个方面:如何处理参数和返回值,如何设计方法签名以及如何记载方法文档。 本章中的大部份内容适用于构造方法和其余普通方法。 与第4章同样,本章重点关注可用性,健壮性和灵活性上。java

大多数方法和构造方法对能够将哪些值传递到其对应参数中有一些限制。 例如,索引值必须是非负数,对象引用必须为非null。 你应该清楚地在文档中记载全部这些限制,并在方法主体的开头用检查来强制执行。 应该尝试在错误发生后尽快检测到错误,这是通常原则的特殊状况。 若是不这样作,则不太可能检测到错误,而且一旦检测到错误就更难肯定错误的来源。git

若是将无效参数值传递给方法,而且该方法在执行以前检查其参数,则它抛出适当的异常而后快速且清楚地以失败结束。 若是该方法没法检查其参数,可能会发生一些事情。 在处理过程当中,该方法可能会出现使人困惑的异常。 更糟糕的是,该方法能够正常返回,但默默地计算错误的结果。 最糟糕的是,该方法能够正常返回可是将某个对象置于受损状态,在未来某个未肯定的时间在代码中的某些不相关点处致使错误。 换句话说,验证参数失败可能致使违反故障原子性(failure atomicity )(条目 76)。github

对于公共方法和受保护方法,请使用Java文档@throws注解来记在在违反参数值限制时将引起的异常(条目 74)。 一般,生成的异常是IllegalArgumentExceptionIndexOutOfBoundsExceptionNullPointerException(条目 72)。 一旦记录了对方法参数的限制,而且记录了违反这些限制时将引起的异常,那么强制执行这些限制就很简单了。 这是一个典型的例子:数组

/**

 * Returns a BigInteger whose value is (this mod m). This method

 * differs from the remainder method in that it always returns a

 * non-negative BigInteger.

 *

 * @param m the modulus, which must be positive

 * @return this mod m

 * @throws ArithmeticException if m is less than or equal to 0

 */

public BigInteger mod(BigInteger m) {

    if (m.signum() <= 0)

        throw new ArithmeticException("Modulus <= 0: " + m);

    ... // Do the computation

}

请注意,文档注释没有说“若是m为null,mod抛出NullPointerException”,尽管该方法正是这样作的,这是调用m.sgn()的副产品。这个异常记载在类级别文档注释中,用于包含的BigInteger类。类级别的注释应用于类的全部公共方法中的全部参数。这是避免在每一个方法上分别记录每一个NullPointerException的好方法。它能够与@Nullable或相似的注释结合使用,以代表某个特定参数可能为空,但这种作法不是标准的,为此使用了多个注解。闭包

在Java 7中添加的Objects.requireNonNull方法灵活方便,所以没有理由再手动执行空值检查。 若是愿意,能够指定自定义异常详细消息。 该方法返回其输入的值,所以能够在使用值的同时执行空检查:oracle

// Inline use of Java's null-checking facility

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

你也能够忽略返回值,并使用Objects.requireNonNull做为知足需求的独立空值检查。less

在Java 9中,java.util.Objects类中添加了范围检查工具。 此工具包含三个方法:checkFromIndexSizecheckFromToIndexcheckIndex。 此工具不如空检查方法灵活。 它不容许指定本身的异常详细消息,它仅用于列表和数组索引。 它不处理闭合范围(包含两个端点)。 但若是它能知足你的须要,那就很方便了。ide

对于未导出的方法,做为包的做者,控制调用方法的环境,这样就能够而且应该确保只传入有效的参数值。所以,非公共方法可使用断言检查其参数,以下所示:工具

// Private helper function for a recursive sort

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

}

本质上,这些断言声称断言条件将成立,不管其客户端如何使用封闭包。与普通的有效性检查不一样,断言若是失败会抛出AssertionError。与普通的有效性检查不一样的是,除非使用-ea(或者-enableassertions)标记传递给java命令来启用它们,不然它们不会产生任何效果,本质上也不会产生任何成本。有关断言的更多信息,请参阅教程assert

检查方法中未使用但存储以供之后使用的参数的有效性尤其重要。例如,考虑第101页上的静态工厂方法,它接受一个int数组并返回数组的List视图。若是客户端传入null,该方法将抛出NullPointerException,由于该方法具备显式检查(调用Objects.requireNonNull方法)。若是省略了该检查,则该方法将返回对新建立的List实例的引用,该实例将在客户端尝试使用它时当即抛出NullPointerException。 到那时,List实例的来源可能很难肯定,这可能会使调试任务大大复杂化。

构造方法是这个原则的一个特例,你应该检查要存储起来供之后使用的参数的有效性。检查构造方法参数的有效性对于防止构造对象违反类不变性(class invariants)很是重要。

你应该在执行计算以前显式检查方法的参数,但这一规则也有例外。 一个重要的例外是有效性检查昂贵或不切实际的状况,而且在进行计算的过程当中隐式执行检查。 例如,考虑一种对对象列表进行排序的方法,例如Collections.sort(List)。 列表中的全部对象必须是可相互比较的。 在对列表进行排序的过程当中,列表中的每一个对象都将与其余对象进行比较。 若是对象不可相互比较,则某些比较操做抛出ClassCastException异常,这正是sort方法应该执行的操做。 所以,提早检查列表中的元素是否具备可比性是没有意义的。 但请注意,不加选择地依赖隐式有效性检查会致使失败原子性( failure atomicity)的丢失(条目 76)。

有时,计算会隐式执行必需的有效性检查,但若是检查失败则会抛出错误的异常。 换句话说,计算因为无效参数值而天然抛出的异常与文档记录方法抛出的异常不匹配。 在这些状况下,你应该使用条目 73中描述的异常翻译( exception translation)习惯用法将天然异常转换为正确的异常。

不要从本条目中推断出对参数的任意限制都是一件好事。 相反,你应该设计一些方法,使其尽量通用。 假设方法能够对它接受的全部参数值作一些合理的操做,那么对参数的限制越少越好。 可是,一般状况下,某些限制是正在实现的抽象所固有的。

总而言之,每次编写方法或构造方法时,都应该考虑对其参数存在哪些限制。 应该记在这些限制,并在方法体的开头使用显式检查来强制执行这些限制。 养成这样作的习惯很重要。 在第一次有效性检查失败时,它所须要的少许工做将会获得对应的回报。

相关文章
相关标签/搜索