无论何时使用输入或者输出,在异常产生后如何关闭资源都是一个麻烦的问题。假设产生了一个 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 类会捕获输入异常,关闭资源并捕获关闭异常,然后抛出一个与之前所忽略异常无关且完全不同的异常。