异常处理代码必须保证其故障安全机制, 其中一条重要的规则如下:
在 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 特性使得实现故障安全异常处理变得更加容易.
来源: https://www.cnblogs.com/nwgdk/p/9004958.html