阿里Java架构师教你怎么校验参数

1 参数校验的意义大多数方法对传递给它们的参数值有限制 。 例如 , 索引值必须非负 , 对象引用必须非空 。

  • 应该清楚地在文档中记录所有这些限制 , 并在方法主体的开头使用检查来实施它们 。
  • 应该在错误发生后尽快找到它们 , 这是一般原则 。 如果不这样做 , 就不太可能检测到错误 , 而且即使检测到错误 , 确定错误的来源也很难 。
若一个无效参数被传递给一个方法
  • 若该方法在执行前检查参数 , 这过程将迅速失败 , 并引发异常
  • 若方法未检查参数 , 可能会在处理过程中出现:莫名其妙的异常而失败正常返回 , 但会悄悄计算错误结果正常返回 , 但会使某对象处于隐患状态 , 可能在未来某不确定时间在某不相关代码点报错 。
【阿里Java架构师教你怎么校验参数】总之 , 若无验证参数 , 可能会违反失败原子性 。
对public、protected方法 , 要在方法说明使用 Javadoc 的 @throws 标签说明如果违反参数值限制时会抛出的异常 。 通常为 IllegalArgumentException、IndexOutOfBoundsException 或 NullPointerException 。 一旦在文档中记录了方法参数上的限制 , 并且记录违反这些限制将引发的异常 , 强加这些限制就很简单了 。
  • 看案例:文档注释并没说「若 m 为空 , mod 将抛NPE」 , 尽管方法确实这么做了 , 作为调用 m.signum() 的副产物 。 该异常记录在外围 BigInterger 类级别的文档注释 。 类级别注释适用于类的所有public方法中的所有参数 。 这可以避免在每个方法上分别记录每个 NullPointerException 而造成混乱 。 它可与 @Nullable 或类似注解结合使用 , 以指示某特定参数可能为 null , 但这种做法并不标准 , 而且使用了多个注解 。
2 最佳实践Java 7 提供 Objects.requireNonNull 不再需手动执行空检查 。
阿里Java架构师教你怎么校验参数文章插图
如果愿意 , 还可自定义异常详情 。 该方法返回其输入 , 所以使用一个值的同时可执行判空:
// 内置 Java 的判空功能this.strategy = Objects.requireNonNull(strategy, "strategy");12也可以忽略返回值并使用 Objects.requireNonNull 作为一个独立判空方法 。
3 边界检查在 Java 9 中 , 边界检查功能被添加到 java.util.Objects 。 该功能由三个方法组成:
  • checkFromIndexSize
  • checkFromToIndex
  • checkIndex
该套工具不如判空方法灵活 。 它不允许自定义异常详细信息 , 仅适用于 List 和数组索引 。 它不处理封闭范围(包含两个端点) 。
4 断言对于未暴露的方法 , 作为包作者 , 你应该控制方法在何时能被调用 , 因此你可以并且也应该确保只传入有效参数值 。 因此 , 非public方法可使用断言检查入参:
阿里Java架构师教你怎么校验参数文章插图
从本质上说 , 这些断言是在声称被断言的条件为 true , 而不管客户端如何调用 。 与普通校验不同的是:
  • 若断言失败 , 会抛 AssertionError
  • 若断言没有作用 , 本质上不存在成本 , 除非通过将 -ea( 或 -enableassertion)标识传递给 java 命令来启用它们
尤其应检查那些尚未由方法调用 , 而是存起供日后使用的参数的有效性 。 例如静态工厂方法 , 它接受 int 数组并返回数组的 List 视图 。 若客户端传入 null , 将抛 NullPointerException , 因为该方法具有显式检查(调用 Objects.requireNonNull) 。 如果省略检查 , 该将返回对新创建的 List 实例的引用 , 该实例将在客户端试图使用它时抛出 NullPointerException 。 到那时 , List 实例的起源很难确定 , 使调试变得复杂 。 构造器就是一种特殊情况 。 务必检查构造器入参有效性 , 避免构造生成实例对象时 , 违背对象的不变性 。
在执行方法前 , 应显式检查参数 , 也有例外 - 有效性检查成本较高或不切实际 , 或检查在计算过程中隐式执行了 。 例如 , 一个为对象 List 排序的方法 , 比如 Collections.sort(List) 。 List 中的所有对象必须相互比较 。 在对 List 排序的过程中 , List 中的每个对象都会与列表中的其他对象进行比较 。 如果对象不能相互比较 , 将抛出 ClassCastException , 这正是 sort 方法应该做的 。 因此 , 没有必要预先检查列表中的元素是否具有可比性 。 但不加区别地依赖隐式有效性检查可能导致失败原子性的丢失 。
有时 , 计算任务会隐式地执行所需的有效性检查 , 但如果检查失败 , 则抛出错误的异常 。 即计算任务由于无效参数值所抛异常 , 与文档中记录的方法要抛出的异常不匹配 。 此时应该使用异常转换将计算任务抛出的异常转换为正确的异常 。