大多数方法和构造函数都对哪些值可以传递到它们的参数有一些限制。例如,索引值必须是非负的,对象引用必须是非空的,这种情况并不少见。您应该清楚地记录所有这些限制,并在方法主体的开头使用检查来执行它们。这是一般原则的一个特例,即您应该在错误发生后尽快检测它们。如果不这样做,就不太可能检测到错误,而且一旦检测到错误,就更难确定错误的来源。
如果一个无效的参数值被传递给一个方法,并且该方法在执行之前检查它的参数,那么它将快速而干净地失败,并出现适当的异常。如果方法未能检查其参数,可能会发生以下几件事。该方法可能会在处理过程中出现令人困惑的异常而失败。更糟的是,该方法可以正常返回,但会默默计算错误的结果。最糟糕的是,该方法可以正常返回,但会使某个对象处于折衷状态,从而在将来某个不确定的时间在代码中某个不相关的点上导致错误。换句话说,如果没有验证参数,可能会违反故障原子性(item76 )。
对于公共方法和受保护的方法,使用Javadoc @throw标记记录异常,如果违反了对参数值的限制,将会引发异常( item74 )。通常,生成的异常将是IllegalArgumentException、IndexOutOfBoundsException或NullPointerException(item72 )。一旦您记录了方法参数上的限制,并且记录了如果违反这些限制将引发的异常,那么执行这些限制就很简单了。这里有一个典型的例子:
注意,doc注释并没有说“如果m为空,mod将抛出NullPointerException”,尽管方法确实是这样做的,这是调用m.signum()的副产品。这个异常记录在类级别的doc注释中,用于包含BigInteger类。类级注释适用于类的所有公共方法中的所有参数。这是避免在每个方法上分别记录每个NullPointerException的混乱的好方法。它可以与@Nullable或类似的注释结合使用,以指示某个特定参数可能为null,但这种做法并不标准,为此使用了多个注释。
Objects.requireNonNull 方法是在Java 7中添加的,非常灵活和方便,因此没有理由再手动执行null检查。*:如果愿意,可以指定自己的异常详细信息。该方法返回它的输入,所以您可以执行一个空检查,同时您使用一个值:
您还可以忽略返回值并使用对象。requireNonNull作为一个独立的null检查,满足您的需要。
在Java 9中,范围检查功能被添加到Java .util. object中。这个工具由三个方法组成:checkFromIndexSize、checkFromToIndex和checkIndex。这个工具不如空检查方法灵活。它不允许您指定自己的异常详细信息,而且它仅用于列表和数组索引。它不处理封闭范围(包含两个端点)。但如果它做了你需要的,它是一个有用的便利。
对于未导出的方法,作为包的作者,您控制方法调用的环境,因此您可以并且应该确保只传递有效的参数值。因此,非公共方法可以使用断言检查它们的参数,如下所示:
从本质上说,这些断言是断言的条件将为真,而不管其客户机如何使用所包含的包。与普通的有效性检查不同,如果断言失败,则会抛出AssertionError。与普通的有效性检查不同,它们没有效果,而且本质上没有成本,除非您启用它们,您可以通过将-ea(或-enableassertion)标志传递给java命令来启用它们。有关断言的更多信息,请参见教程[断言]。
特别重要的是检查那些不是由方法使用,而是存储起来供以后使用的参数的有效性。例如,考虑第101页中的静态工厂方法,它接受一个int数组并返回数组的List视图。如果客户机传入null,该方法将抛出NullPointerException,因为该方法具有显式检查(调用object . requirenonnull)。如果省略了检查,该方法将返回对新创建的List实例的引用,该实例将在客户端试图使用它时抛出NullPointerException。到那时,列表实例的起源可能很难确定,这可能会使调试任务变得非常复杂。
构造函数代表了一种特殊的情况,即您应该检查要存储起来供以后使用的参数的有效性。检查构造函数参数的有效性对于防止构造违背类不变量的对象非常重要。
在执行方法的计算之前,应该显式地检查方法的参数,这条规则也有例外。一个重要的例外是在这种情况下,有效性检查将是昂贵的或不切实际的,并且检查是在计算过程中隐式执行的。例如,考虑一个对对象列表进行排序的方法,比如Collections.sort(list)。列表中的所有对象必须相互比较。在对列表排序的过程中,列表中的每个对象都会与列表中的其他对象进行比较。如果对象不能相互比较,其中一个比较将抛出ClassCastException,这正是sort方法应该做的。因此,没有必要预先检查列表中的元素是否具有可比性。但是,请注意,不加区别地依赖隐式有效性检查可能导致失败原子性的丢失(item76 )。
有时,计算隐式地执行所需的有效性检查,但如果检查失败,则抛出错误的异常。换句话说,计算由于无效参数值而自然抛出的异常与记录方法要抛出的异常不匹配。在这种情况下,您应该使用项目73中描述的异常翻译习语来将自然异常转换为正确的异常。
不要从这一项推断对参数的任意限制是一件好事。相反,你应该把方法设计得既通用又实用。对参数施加的限制越少越好,假设该方法可以对它所接受的所有参数值进行合理的处理。然而,一些限制常常是实现抽象的内在限制。
总而言之,每次编写方法或构造函数时,都应该考虑对其参数存在哪些限制。您应该记录这些限制,并在方法主体的开头显式地检查它们。养成这样做的习惯是很重要的。它所涉及的少量工作将在有效性检查第一次失败时连本带利地偿还。
本文写于2019.7.16,历时1天
网友评论