Jakob Jenkov 2014-10-18
当你使用完 流(Streams) 或 读写器(Readers / Writers) 后,需要正确地关闭它们。这通常通过调用 close() 方法完成。然而,这需要一些额外的考虑。
请看下面这段代码:
InputStream input = new FileInputStream("c:\\data\\input-text.txt");
int data = input.read();
while(data != -1) {
// 对数据进行某些操作...
doSomethingWithData(data);
data = input.read();
}
input.close();
乍一看,这段代码似乎没有问题。但如果在 doSomethingWithData() 方法内部抛出了异常呢?没错!InputStream 将永远不会被关闭!
为了避免这种情况,你可以将代码重写如下:
InputStream input = null;
try {
input = new FileInputStream("c:\\data\\input-text.txt");
int data = input.read();
while(data != -1) {
// 对数据进行某些操作...
doSomethingWithData(data);
data = input.read();
}
} catch(IOException e) {
// 处理异常... 比如记录日志,或者重新抛出等
} finally {
if(input != null)
input.close();
}
注意,现在 InputStream 是在 finally 块中关闭的。无论 try 块中发生了什么,finally 块都会被执行,因此 InputStream 总是会被关闭。
但还有一个问题:如果 close() 方法本身抛出了异常怎么办?比如流已经被关闭了?为了解决这个问题,你还需要将 close() 调用也包裹在一个 try-catch 块中,如下所示:
} finally {
try {
if(input != null)
input.close();
} catch(IOException e) {
// 可以处理异常,也可以直接忽略
}
}
如你所见,一旦加入正确的异常处理逻辑,遍历一个 InputStream(或 OutputStream)的代码会变得相当丑陋。这种冗长且重复的异常处理代码散布在整个项目中并不理想。万一某个赶时间的开发者偷工减料,跳过了异常处理呢?
此外,想象一下:如果首先从 doSomethingWithData() 抛出了一个异常,第一个 catch 块会捕获它,然后 finally 块尝试关闭 InputStream。但如果此时 input.close() 也抛出了异常,那么应该将哪一个异常向上传播呢?
幸运的是,这个问题有解决方案,那就是使用 “异常处理模板”(Exception Handling Templates)。你可以创建一个能正确关闭流的异常处理模板,这个模板只需编写一次,之后在整个代码中复用即可。简洁又可靠。
从 Java 7 开始的 Java IO 异常处理
从 Java 7 开始,Java 引入了一种新的异常处理机制,称为 “带资源的 try 语句”(try-with-resources)。该机制专门用于处理那些使用后必须正确关闭的资源(如 InputStream、OutputStream 等)的异常处理。
你可以在我写的这篇教程中了解更多:《Java 中的 Try-With-Resources》。