阅读经典——《Effective Java》10
异常是面向对象编程中非常重要的一环,合理使用异常,可以提高程序的可读性、可靠性和可维护性。本文我们来讨论在Java中如何合理使用异常。
- 只针对异常的情况才使用异常
- 正确使用受检异常和运行时异常
- 优先使用标准的异常
- 异常转译和异常链
只针对异常的情况才使用异常
异常应该只用于异常的情况,而不是正常的控制流。任何试图使用异常来简化正常控制流的行为都是不值得提倡的。考虑下面的情况:
try {
Iterator<Foo> i = collection.iterator();
while(true) {
Foo foo = i.next();
...
}
} catch (NoSuchElementException e) {
}
使用异常检查迭代器是否遍历结束,这是很愚蠢的做法,可读性很差,而且在性能上可能更糟。Iterator
作为一个设计良好的API,不可能鼓励用户这样使用,因此它提供了hasNext
方法用来显式判断遍历是否结束。这也给API设计者一定的启发,不要让你的用户被迫使用异常来实现正常的控制流。
正确使用受检异常和运行时异常
Java程序设计语言提供了三种可抛出结构(throwable):受检异常(checked exception)、运行时异常(runtime exception)和错误(error)。
受检异常必须必须被try-catch块包围,用户有责任处理这个异常或继续向外抛出。这是对API用户的一种暗示:抛出异常是该方法的一种可能结果,请务必小心。
运行时异常用来表明编程错误,通常是不可恢复的。大多数运行时异常都是由于前提违例,前提违例是指API的用户没有遵守API规范建立的约定。例如,数组访问约定了下标值必须在0和数组长度减1之间,ArrayIndexOutOfBoundsException
表明用户违反了这个前提。
错误往往被JVM保留用于表示资源不足、约束失败、或者其它使程序无法继续执行的条件。这是系统错误,我们不应该继承Error
。
自定义异常也是一个完整意义上的对象,因此我们可以在它上面定义各种方法,以提供额外的信息供用户查看。
优先使用标准的异常
Java类库为我们提供了一组基本的运行时异常,它们可以满足绝大多数API的异常抛出需求。我们应该尽量重用它们,而不是自定义全新的异常。因为这会使你的API更容易被用户理解,而且降低类加载的开销。常用的异常列表如下。
异常 | 使用场合 |
---|---|
IllegalArgumentException | 非null的参数值不正确 |
IllegalStateException | 对于方法调用而言,对象状态不合适 |
NullPointerException | 在禁止使用null的情况下参数值为null |
IndexOutOfBoundsException | 下标参数值越界 |
ConcurrentModificationException | 在禁止并发修改的情况下,检测到对象的并发修改 |
UnsupportedOperationException | 对象不支持用户请求的方法 |
异常转译和异常链
如果高层无法处理低层抛出的异常,就应该继续抛出。但抛出时建议使用异常转译:更高层的实现应该捕获低层的异常,同时抛出可以按照高层抽象进行解释的异常。
下面的异常转译例子取自AbstractSequentialList
类,在这个例子中,按照List<E>
接口中get
方法的规范要求,异常转译是必需的。使用该类的用户应该看到IndexOutOfBoundsException
而不是NoSuchElementException
。
/**
* 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);
}
}
上面这种写法唯一的缺点是会丢失低层异常信息,不过我们可以用异常链来弥补这个缺点。我们的自定义异常应该提供一个接收Throwable
参数的构造方法,把该参数传给父类的构造器,代码如下。这样用户使用我们的API时就可以用getCause
访问到异常的底层原因。
// Exception with chaining-aware constructor
class HigherLevelException extends Exception {
HigherLevelException(Throwable cause) {
super(cause);
}
}
关注作者或文集《Effective Java》,第一时间获取最新发布文章。