充分发挥异常的优点,可以提高程序的可读性、可靠性和可维护性。如果使用不当,它们会带来负面影响。
1.只针对异常的情况才使用异常
以下这段代码用抛出捕获异常的方式来达到终止无线循环的目的。
tyr {
int i = 0;
while(true) {
range[i++].climb();
}
} catch (ArrayIndexOutOfBoundsException e) {
}
2.对可恢复的情况使用受检异常,对编程错误使用运行时异常
Java 提供了三种可抛出结构(throwable):
- 受检异常(checked exception)
- 运行时异常(runtime exception)
- 错误(error)
如果期望调用者能够适当地恢复,对于这种情况使用受检异常。强迫调用者在catch子句中处理该异常,或者将其传播出去。
实现的所有未受检的抛出结构都应该是 RuntimeException 的子类。
3.避免不必要地使用受检的异常
过分使用受检异常会使API使用起来非常不方便。因为不介意调用失败而导致线程终于,所以可以重构为,把这个抛出异常分成两个方法,其中一个方法是返回 boolean,表明是否应该抛出异常。
4.优先使用标准的异常
经常被重用的异常:
- IllegalArgumentException
- IllStateExcetption:如果因为接受对象的状态而使调用非法,通常会抛出这个异常。例如,如果某个对象被正确初始化之前,调用者就企图使用这个对象,就会抛出异常。
- NullPointerException
- IndexOutOfBoundsException
- ConcurrentModificationException:如果一个对象被设计为专门用于单线程或者与外部同步机制配合使用,一旦发现它正在被并发修改,就应该抛出异常
- UnsupportedOperationException:对象不支持所请求的操作,就抛出这个异常。
- ArithmeticException
- NumberFormatException
5.抛出与抽象相对应的异常
更高层的实现应该捕获底层异常,同时抛出可以按照高层抽象进行解释的异常。这种做法叫异常转译。
try {
} catch (LowerLevelException e) {
throw new HigherLevelException(..);
}
#### 6.每个方法抛出的异常都要有文档
始终要单独地声明受检的异常,并且利用 Javadoc 的 @throws 标记,准确地记录下抛出每个异常的条件。
#### 7.在细节消息中包含能捕获失败的信息
当程序由于未被捕获的异常而失败的时候,系统会自动打印该异常的堆栈轨迹,在堆栈轨迹中包含该异常的字符串表示法,即 toString 方法,通常应该包含异常的类名,加细节消息,包括参数和域的信息。
#### 8.努力使失败保持原子性
> 对于受检异常,当对象抛出异常后,通常期望仍然保持一种定义良好的可用状态,因为调用者其他能从这种异常中进行恢复,这种属性称为具有失败原子性。
设计一个不可变的对象,对于可变对象,执行操作之前进行参数有效性检查。
#### 9.不要忽略异常
空的catch块会使异常达不到应有的目的。每当见到空catch块,应该警钟长鸣,至少,要包含一条说明,解释为何可以忽略这个异常。