方法抛出的异常与所完成的任务没有明显的关联会让人不知所措。这种情形常常发生在方法转发了底层方法抛出的异常。这不仅让人窘迫,还会破坏高层API的实现细节。如果在下一个版本中改变了高层实现,则它所抛出的异常也会改变,这有可能破获它的客户端代码。
为避免这种情况,高层代码应捕获低层的异常,并且在高层对低层异常进行抽象,用抽象了的异常代替低层异常,向上抛出。这个习惯用法称为异常转换(exception translation)
// Exception Translation
try {
// Use lower-level abstraction to do our bidding
...
} catch(LowerLevelException e) {
throw new HigherLevelException(...);
}
这里是取自AbstractSequentialList类中一个异常转换的示例,这个类是List接口的骨架实现(skeletal implementation),在这个例子中,List<E>接口的get方法规范明确了这个异常转换。
/**
* 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 chain),低层异常可能引起高层异常,异常链有助于调试这种问题,低层异常(原因)传给高层异常,高层异常提供一个访问方法(Throwable.getCause)以获得低层异常:
// Exception Chaining
try {
... // Use lower-level abstraction to do our bidding
} catch (LowerLevelException cause) {
throw new HigherLevelException(cause);
}
在高层异常的构造器中将原因传给chaining-aware超类的构造器,最终传到Throwable的chaining-aware的构造器,如Throwable(Throwable):
// Exception with chaining-aware constructor
class HigherLevelException extends Exception {
HigherLevelException(Throwable cause) {
super(cause);
}
}
大部分标准异常都有chaining-aware的构造器。若没有,可调用Throwable的initCause方法设置原因。异常链不仅让你能编程访问异常原因(使用getCause方法),还能将原因的堆栈踪迹集成至高层异常。
虽然异常转换大大优于低层异常的盲目传播,但也不能滥用。只要可能,处理底层异常的最好的方法是避免它们,保证低层方法正确运行,有时你可以在高层方法中先检查传给低层方法的参数的有效层来做到这一点。
如果无法阻止低层的异常,则较好的方法是让高层避开异常,悄悄运行,从而隔开高层方法中与低层方法中的出现的异常问题,在这用情况下,适合用java.util.logging之类的设施在日志中记下这些异常。这样管理员可以研究问题,而将方法的调用者及终端用户与异常隔开。
总之,如果不能阻止或处理低层异常,请使用异常转换,除非低层的所有异常都适合高层。异常链对这两种情况都是最好的,一方面它能让你抛出合适的高层异常,又能获得潜在的原因,这可用于错误分析。