无论何时使用输入或者输出,在异常产生后如何关闭资源都是一个麻烦的问题。假设产生了一个 IOException,接下来在关闭资源时,close 方法又抛出了另一个异常。
那么实际上会捕获哪个异常呢?在 Java 中,finally 分支中抛出的异常会丢弃掉之前的异常。这不仅听上去不合理,实际上也确实不太合理。毕竟,用户对原始的异常会更感兴趣。
我们通过下面示例来验证:
package com.hxstrive.jdk7.exception; import java.io.*; /** * 异常处理 * @author hxstrive.com */ public class ExceptionDemo1 { public static void main(String[] args) throws Exception { InputStream inputStream = null; OutputStream outputStream = null; try { // input.txt 是一个不存在的文件 inputStream = new FileInputStream(new File("D:\\input.txt")); // 这里会抛出 FileNotFoundException int read = inputStream.read(); System.out.println("read:" + read); outputStream = new FileOutputStream(new File("D:\\output.txt")); // 这里不会执行 } finally { // outputStream.close(); // inputStream.close(); } } }
执行上面代码,将会抛出 “FileNotFoundException”,如下:
Exception in thread "main" java.io.FileNotFoundException: D:\input.txt (系统找不到指定的文件。) at java.io.FileInputStream.open(Native Method) at java.io.FileInputStream.<init>(FileInputStream.java:146) at com.hxstrive.jdk7.exception.ExceptionDemo1.main(ExceptionDemo1.java:16)
如果我们将 finally 子语句中的注释去掉,尝试关闭 outputStream 资源,此时会出现空指针异常,如下:
Exception in thread "main" java.lang.NullPointerException at com.hxstrive.jdk7.exception.ExceptionDemo1.main(ExceptionDemo1.java:22)
通过异常信息,你会发现 FileNotFoundException 异常不存在了。
在 Java7 中,try-with-resources 语句修正了这个行为。当 Autocloseable 对象的 close 方法抛出异常时,原来的异常会被重新抛出,而调用 close() 方法产生的异常会被捕获,并被标注为 “被忽略” 的异常。
当捕获到首次异常时,你可以通过调用 getSuppressed() 方法来获取那些二次异常:
package com.hxstrive.jdk7.exception; import java.io.*; import java.util.Arrays; /** * 异常处理 * @author hxstrive.com */ public class ExceptionDemo2 { public static void main(String[] args) throws Exception { OutputStream outputStream = null; try ( InputStream inputStream = new FileInputStream(new File("D:\\input.txt")) // 抛出文件不存在异常 ) { int read = inputStream.read(); System.out.println("read:" + read); outputStream = new FileOutputStream(new File("D:\\output.txt")); // 不会执行 } catch (Exception e) { Throwable[] suppressed = e.getSuppressed(); System.out.println(Arrays.toString(suppressed)); // 打印异常链 e.printStackTrace(); } finally { outputStream.close(); // 抛出空指针异常 } } }
执行上面代码,抛出异常如下:
[] java.io.FileNotFoundException: D:\input.txt (系统找不到指定的文件。) at java.io.FileInputStream.open(Native Method) at java.io.FileInputStream.<init>(FileInputStream.java:146) at com.hxstrive.jdk7.exception.ExceptionDemo2.main(ExceptionDemo2.java:16) Exception in thread "main" java.lang.NullPointerException at com.hxstrive.jdk7.exception.ExceptionDemo2.main(ExceptionDemo2.java:26)
如果不能使用 try-with-resources 语句,又希望自己实现这样的机制,你可以调用:
ex.addSuppressed(secondaryException);
注意:Throwable、Exception、RuntimeException 和 Error 类的构造参数都可以接受两个参数,分别用来禁用忽略异常和禁用堆栈跟踪。Throwable 构造方法签名:
protected Throwable(String message, Throwable cause, boolean enableSuppression, // 禁用忽略异常 boolean writableStackTrace) { //... }
⚠️注意:
(1)当忽略异常被禁用时,调用 addSuppressed() 就不会有效果,并且 getSuppressed() 方法会返回一个长度为 0 的数组。当禁用堆栈跟踪时,调用 fillInStackTrace() 不会有效果,并且getStackTrace() 方法会返回一个长度为 0 的数组。这可以用于因内存不够而产生的 VM 错误,或者 VM 上使用异常来中断嵌套方法调用的编程语言。
(2)只有当二次异常没有被主动破坏时,你才能检测它们。尤其是,如果你使用了一个 Scanner 并且当输入失败且随后的关闭也失败时,Scanner 类会捕获输入异常,关闭资源并捕获关闭异常,然后抛出一个与之前所忽略异常无关且完全不同的异常。