什么是异常转译(exception translation)
在Java中,我们通常使用try-catch
语句捕获异常,进行异常处理。但有些时候,我们使用try-catch
捕获一个异常,但却不进行异常处理,反而是抛出另一个异常,这就称为异常转译。如下所示,
try {
Method(); //某个会抛出Exception1异常的函数
} catch (Exception1 e) {
throw new Exception2();
}
从上述代码我们很容易理解异常转译
这个名字的内涵——将异常Exception1
转译为Exception2
。
为什么要进行异常转译
第一次看见这样的代码可能会产生疑惑——使用try-catch
语句通常是用来捕获异常并进行处理,但上述代码却在捕获异常后又抛出了另一个异常,既然终究会抛出一个异常,那么何必再多此一举使用try-catch
呢?直接抛出Exception1
不好吗?
这样做的原因是,在某些时候,高层实现和低层异常之间的关系不明显,直接抛出低层异常会使调用高层实现的客户端看到异常时不知所措。
下面用JDK的AbstractSequentialList类
中的例子解释上面抽象的描述。
该类中有一个get
方法,其具体实现如下
public E get(int index) {
ListInterator<E> i = listIterator(index);
try {
return i.next();
} catch (NoSuchElementException e) {
throw new IndexOutOfBoundsException("Index: " + index);
}
}
上述代码接收一个int
型的参数index
,返回集合中下标为index
的元素。值得注意的是,该函数要求index
的值大于等于0,但小于集合中元素的总数。
next()
方法的抛出一个NoSuchElementException
异常,倘若不使用异常转译,调用get
方法的客户端将直接得到一个NoSuchElementException
的异常,这样会使得客户端对异常产生的原因感到迷惑。通过异常转译,使用try-catch
捕获一个NoSuchElementException
异常并将其转换为IndexOutOfBoudsException
异常,调用get
方法的客户端看见这个异常,便能很快知道是由于参数index
超出了规定的范围而导致的该异常,一目了然。
一种更加推荐的做法
在上述代码中,抛出的IndexOutOfBoundsException
异常只包含index
的信息,一种更加推荐的做法是:
调用构造函数IndexOutOfBoundsException(Throwable cause)
,将低层的NoSuchElementException
异常传入高层异常IndexOutOfBoundsException
。当客户端调用get
方法同时得到一个IndexOutOfBoundsException
时,可以调用Throwable
的getCause
方法得到低层的异常NoSuchElementException
。这种保留低层异常的方式能够为客户端提供更多的关于异常的信息,方便客户端找到异常产生的原因。
值得注意的一点
通常我们只使用try-catch
捕获受查异常,但是在异常转译中,我们使用try-catch
捕获并转译的异常却没有这个限制,只要拥有足够的理由对某个异常进行转译,那么即使是RuntimeException
,也是可以使用try-catch
去捕获并转译的。
(上述例子中转译的NoSuchElementException
就是一个RuntimeException
.)
参考资料
《Effective Java》第三版