异常处理代码必须保证其故障安全机制,其中一条重要的规则如下:
在
try-catch-finally
块抛出的最后一个异常将会在调用堆栈中传递。
所有早期异常将会消失。
如果从一个catch
或finally
块抛出一个异常,那么这个异常可能会导致try
块中捕获的异常隐藏。这会在你试图确定异常的原因时产生误导。
下面是non-fail-safe
异常处理的经典示例:
InputStream input = null;
try {
input = new FileInputStream( "myFile.txt" );
/* do something with the stream */
}
catch ( IOException e ) {
throw new WrapperException( e );
}
finally {
try {
input.close();
}
catch ( IOException e ) {
throw new WrapperException( e );
}
}
如果FileInputStream
构造器抛出一个FileNotFoundException
异常,你认为会发生什么?
首先会执行catch
块,该块只会重新抛出包装在WrapperException
中的异常。
其次将执行finally
块来关闭输入流。但是,由于FileInputStream
构造器抛出了一个FileNotFoundException
异常,引用变量"input"将为null
。结果将是从finally
块中抛出NullPointerException
异常。NullPointerException
不会被catch ( IOException e )
子句捕获,所以它将会在调用堆栈中传递。而第一个 catch 块中抛出的WrapperException
将会消失。
处理这种情况的正确方式是,在调用任何方法之前,先检查在try
块中分配的引用是否为null
。比如像下面这样:
InputStream input = null;
try {
input = new FileInputStream("myFile.txt");
//do something with the stream
}
catch(IOException e) {
//first catch block
throw new WrapperException(e);
}
finally {
try {
if(input != null) input.close();
}
catch(IOException e){
//second catch block
throw new WrapperException(e);
}
}
但即使是这样处理同样还是会有问题,让我们先假设"myFile.txt"文件存在,因此"input"引用现在指向一个有效的FileInputStream
。同样我们也假设处理输入流时引发了异常,这时第一个 catch 子句捕获后处理并抛出WrapperException
,在将WrapperException
传递到调用堆栈之前,还要先执行finally
子句。如果input.close()
调用失败,那么它将会抛出IOException
并且被第二个 catch 子句捕获并抛出WrapperException
,这时从第一个 catch 子句中抛出的WrapperException
再次消失,只有第二个 catch 抛出的WrapperException
才会被传递到调用堆栈中。
如你所见,故障安全(fail safe)异常处理并不总是无价值的。InputStream
处理示例甚至不是你可以遇到的最复杂的示例。JDBC 中的事务有更多错误的可能性。当尝试提交、然后回滚,最后尝试关闭连接时,可能会出现异常。所有这些可能出现的异常都应该由异常处理代码来处理,因此,他们都不会使第一个被抛出异常消失。这样做的一种方法是确保抛出的最后一个异常包含以前抛出的所有异常,这样开发人员就可以了解错误原因。
BTW,Java 7 中的“try-with-resources”特性使得实现故障安全异常处理变得更加容易。
原文链接:http://tutorials.jenkov.com/java-exception-handling/fail-safe-exception-handling.html