3 边界检查
在 Java 9 中,边界检查功能被添加到
java.util.Objects
。该功能由三个方法组成:
- checkFromIndexSize
- checkFromToIndex
- checkIndex
- 该套工具不如判空方法灵活。它不允许自定义异常详细信息,仅适用于 List 和数组索引。它不处理封闭范围(包含两个端点)。
4 断言
对于未暴露的方法,作为包作者,你应该控制方法在何时能被调用,因此你可以并且也应该确保只传入有效参数值。因此,非public方法可使用断言检查入参:
从本质上说,这些断言是在声称被断言的条件为 true,而不管客户端如何调用。与普通校验不同的是:
若断言失败,会抛 AssertionError
若断言没有作用,本质上不存在成本,除非通过将 -ea( 或 -enableassertion)标识传递给 java 命令来启用它们
尤其应检查那些尚未由方法调用,而是存起供日后使用的参数的有效性。例如静态工厂方法,它接受 int 数组并返回数组的 List 视图。若客户端传入 null,将抛 NullPointerException,因为该方法具有显式检查(调用 Objects.requireNonNull)。如果省略检查,该将返回对新创建的 List 实例的引用,该实例将在客户端试图使用它时抛出 NullPointerException。到那时,List 实例的起源很难确定,使调试变得复杂。
构造器就是一种特殊情况。务必检查构造器入参有效性,避免构造生成实例对象时,违背对象的不变性。
在执行方法前,应显式检查参数,也有例外 - 有效性检查成本较高或不切实际,或检查在计算过程中隐式执行了。
例如,一个为对象 List 排序的方法,比如 Collections.sort(List)。List 中的所有对象必须相互比较。在对 List 排序的过程中,List 中的每个对象都会与列表中的其他对象进行比较。如果对象不能相互比较,将抛出 ClassCastException,这正是 sort 方法应该做的。因此,没有必要预先检查列表中的元素是否具有可比性。但不加区别地依赖隐式有效性检查可能导致失败原子性的丢失。
有时,计算任务会隐式地执行所需的有效性检查,但如果检查失败,则抛出错误的异常。即计算任务由于无效参数值所抛异常,与文档中记录的方法要抛出的异常不匹配。此时应该使用异常转换将计算任务抛出的异常转换为正确的异常。
5 总结
请勿觉得对参数的任意限制都是好事,而应把方法设计得既通用又实用。
对参数施加的限制越少越好,假设该方法可对它所接受的所有参数值进行合理的处理。然而,一些限制常常是实现抽象的内在限制。
每次编写方法或构造器时,都应考虑参数存在哪些限制。你应该在文档中记录这些限制,并在方法主体的开头显式检查。养成这样的习惯!这一少量工作将在有效性检查出现第一次失败时连本带利地偿还给你!