Effective Java(九)异常

Item 69 异常只用于异常

异常只用于异常的情况,不要用 try-catch 捕获 ArrayIndexOutOfBoundsException 并且不做任何处理这种方式来跳出数组遍历。为什么不用 for-each 循环呢?

设计良好的 API 不应该强迫它的客户端为了正常的控制流程而使用异常。

例如:

1
2
3
4
for ( Iterator<Foo> i = collection.iterator(); i.hasNext(); ){
Foo foo = i.next();
...
}

hasNext方法是一个状态测试机。如果 Iterator API 没有设计 hasNext 方法,那么客户端代码将会变得很丑:

1
2
3
4
5
6
7
8
9
/* Do not use this hideous code for iteration over a collection! */
try {
Iterator<Foo> i = collection.iterator();
while ( true ) {
Foo foo = i.next();
...
}
} catch ( NoSuchElementException e ) {
}

Item 70 对可恢复的情况使用checked exception,对编程错误使用unchecked exception(runtime exception)

使用 checked exception 还是 unchecked exception 的一个基本原则是,你是不是想让程序恢复。如果是,使用 checked exception。

ArrayIndexOutOfBoundsExceptionNullPointerException 这类 unchecked exception 异常,可以通过优化代码实现来避免,不应该捕获。而像 IOException 是不可避免的,应当捕获并处理。


Item 71 避免对 checked exception 不必要的使用

正如前面所说的,异常只用于异常情况。有些 try-catch 实际上可以分解重构成 if-else,对于真正有异常的部分才进入 try-catch 块。不要过度使用 try-catch


Item 72 优先使用标准的异常

Java 平台类库提供了一组基本的 unchecked exception,它们满足了绝大多数 API 的异常抛出需求。应该尽可能使用。

异常 使用场合
IllegalArgumentException 非 null 的参数值不正确(如接收非负数的方法传入了-1)
IllegalStateException 不适合方法调用的对象状态(如对象未被正确初始化前就被调用)
NullPointerException 在禁止使用 null 的情况下参数值为 null
IndexOutOfBoundsExecption 下标参数值越界
ConcurrentModificationException 在禁止并发修改的情况下,检测到对象的并发修改
UnsupportedOperationException 对象不支持用户请求的方法

Item 73 异常转译和异常链

高层的实现应该捕获底层的异常,同时抛出可以按照高层抽象进行解释的异常。这种做法称为 异常转译 (exception translation)

例如下面的例子,高层的将 get(i) 将底层 i.next()NoSuchElementException 转译成 IndexOutOfBoundsException 并抛出。

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Returns the element at the specified position in this list.
* @throws IndexOutOfBoundsException if the index is out of range
* ({@code index < 0 || index >= size()}).
*/
public E get(int index) {
ListIterator<E> i = listIterator(index);
try {
return(i.next() );
} catch (NoSuchElementException e) {
throw new IndexOutOfBoundsException("Index: " + index);
}
}

在转译时,可以形成 异常链(exception chaining),将底层异常带出去。如果低层的异常对于调试导致高层异常的问题非常有帮助,使用异常链就很合适。

1
2
3
4
5
6
// Exception Chaining
try {
... // Use lower-level abstraction to do our bidding
} catch (LowerLevelException cause) {
throw new HigherLevelException(cause);
}

但也不能滥用异常链,使用原则是:优先处理异常,无法处理时才向上抛出或转译抛出,如果底层异常对高层调试有帮助,才用异常链。


Item 74 每个方法抛出的异常都需要创建文档

使用 Javadoc 的 @throws 标签记录一个方法可能抛出的每个unchecked exception,但是不要使用 throws 关键字将 unchecked exception 包含在方法的声明中。

1
2
3
4
5
6
7
8
9
10
11
12
// do this
/**
* @throws may cause NullPointerException
**/
public String foo(){

}

// don't do this
public String foo thorws NullPointerException(){

}

Item 75 异常输出

输出异常的细节信息应该包含有用的所有参数和字段的值。例如 IndexOutOfBoundsException 应该包含下界、上界以及实际使用的下标值。

1
2
3
4
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 5, Size: 2
at java.util.ArrayList.rangeCheck(ArrayList.java:657)
at java.util.ArrayList.get(ArrayList.java:433)
at test.Test.main(Test.java:18)

Tips:千万不要在细节消息中包含密码、密钥以及类似的信息!


Item 76 failure atomic

一般而言,失败的方法调用应该使对象保持在被调用之前的状态。具有这种属性的方法被称为具有 失败原子性(failure atomic)。也就是说,出现异常并捕获,之后程序恢复,不会因此有其他任何状态的改变。

最简单的方法是用不可变对象。不可变对象的状态永远是一致的。对于在可变对象,获得失败原子性最常见的办法是,在执行操作之前检查参数的有效性 (Item 49)。这可以使得在对象的状态被修改之前,先抛出适当的异常。

1
2
3
4
5
6
7
public Object pop() {
if ( size == 0 )
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; /* Eliminate obsolete reference */
return(result);
}

如果取消对初始大小(size)的检查,当这个方法试图从一个空栈中弹出元素时,它仍然会抛出异常。然而,这将会导致 size 字段保持在不一致的状态(负数)之中,从而导致将来对该对象的任何方法调用都会失败。

总而言之,想办法让在异常抛出前后,对象的状态保持一致。你可以提前检查(像上面那样),可以调整计算处理的顺序,可以在对象的一份临时拷贝上执行操作,操作完再去代替对象本身。或者是,编写恢复对象状态的代码(不常用)。

虽然一般情况下都希望实现失败原子性,但并非总是可以做到。如果两个线程企图在没有适当的同步机制的情况下,并发地修改同一个对象,这个对象就有可能被留在不一致的状态之中。因此,在捕获了 ConcurrentModificationException 异常之后再假设对象仍然是可用的,这就是不正确的。


Item 77 不要忽略异常

忽略一个异常非常容易,只需将方法调用通过 try 语句包围起来,并包含一个空的 catch 块。但是,最好不要这么做。空的 catch 块会使异常达不到应有的目的。

1
2
3
4
5
// Empty catch block ignores exception - Highly suspect!
try {
...
} catch ( SomeException e ) {
}

即使确实不需要做任何处理,把异常记录下来还是明智的做法,因为如果这些异常经常发生,你就可以调查异常的原因。

如果异常确实也不需要记录下来,才选择忽略异常,catch 块中应该包含一条注释,说明为什么可以这么做,并且变量应该命名为 ignored。

1
2
3
4
5
try {
numColors = f.get( 1L, TimeUnit.SECONDS );
} catch ( TimeoutException | ExecutionException ignored ) {
// Use default: minimal coloring is desirable, not required
}
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×