Effective Java 第三版——72. 赞成使用标准异常
Tips
书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code
注意,书中的有些代码里方法是基于Java 9 API中的,所以JDK 最好下载 JDK 9以上的版本。
72. 赞成使用标准异常
专家级程序员与经验较少的程序员之间的一个区别是,专家力争并通常实现高度的代码重用。代码重用是一件好事,异常也不例外。Java类库提供了一组异常,涵盖了大多数API的异常抛出需求。
重用标准异常有几个好处。其中最主要的是,它使你的API更容易学习和使用,因为它符合程序员已经熟悉的既定约定。其次,使用你的API的程序更容易阅读,因为它们不会因为不熟悉的异常而混乱。最后(也是最不重要的),更少的异常类意味着更小的内存占用和更少的加载类的时间。
最常用的异常是IllegalArgumentException(条目 49)。 当调用者传入一个不合适的参数值时,通常抛出这个异常。 例如,如果调用者在表示某个操作重复次数的参数中传递了一个负数,则抛出此异常。
另一个常用的异常是IllegalStateException。 如果由于接收对象的状态而调用是非法的,则通常会抛出异常。 例如,如果调用者试图使用尚未正确初始化之前的对象时,则抛出这个异常。
可以说,每个错误的方法调用都可以归结为非法参数或状态,但是还有一些异常通常用于某些类型的非法参数和状态。如果调用者在禁止null值的参数中传递null,那么按照惯例,抛出NullPointerException,而不是IllegalArgumentException异常。类似地,如果调用者将表示索引的参数中的超出范围的值传递给序列,则应该抛出IndexOutOfBoundsException,而不是IllegalArgumentException。
另一个可重用异常是ConcurrentModificationException。如果一个对象被设计为由单个线程使用(或与外部同步),并且检测到它正在被并发地修改,则应该抛出该对象。这个异常最多是一个提示,因为无法可靠地检测并发修改。
最后一个需要注意的标准异常是UnsupportedOperationException。如果对象不支持尝试的操作,则抛出此异常。它很少使用,因为大多数对象都支持它们的所有方法。此异常用于无法实现由其实现的接口定义的一个或多个Optional操作的类。例如,如果有人试图从仅支持追加(append-only )的列表中删除元素,则将抛出此异常。
不要直接重用Exception、RuntimeException、Throwable或Error。将这些类视为抽象类。你不能对这些异常进行可靠的测试,因为它们是方法可能抛出的其他异常的父类。
此表总结了最常见的重用异常:
异常 | 使用场景 |
---|---|
IllegalArgumentException | 不合适的非null参数值 |
IllegalStateException | 方法调用状态不适合的对象 |
NullPointerException | 再禁止使用null的情况下参数值为null |
IndexOutOfBoundsException | 索引参数值越界 |
ConcurrentModificationException | 在禁止并发修改对象的地方检测到该对象被并发修改 |
UnsupportedOperationException | 对象不支持方法 |
虽然到目前为止,这些是最常见的重用异常,但是在环境允许的情况下也可以重用其他异常。例如,如果正在实现诸如复数或有理数之类的算术对象,那么重用ArithmeticException和NumberFormatException是合适的。如果一个异常符合你的需要,那么继续使用它,但前提是抛出它的条件与异常文档描述一致:重用必须基于文档化的语义,而不仅仅是基于名称。另外,如果想添加更多的细节,可以随意子类化标准异常(条目 75),但是请记住异常是可序列化的(第12章)。这本身就是,如果没有充分理由,不要编写自己的异常类的原因。
选择重用哪个异常可能比较棘手,因为上面表格中的“使用场景”似乎并不相互排斥。考虑表示一副纸牌的对象的情况,假设有表示发一手牌的方法,该方法参数是一手牌的纸牌数量。如果调用者传递的值大于整副牌中剩余的牌的数量,则可以将其解释为IllegalArgumentException (handSize参数值过大),或者是IllegalStateException(纸牌中包含的牌太少)。在这种情况下,规则是,如果没有参数值,则抛出IllegalArgumentException,否则抛出IllegalArgumentException异常。