Java IO:异常处理

更新于 2025-12-26

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)。该机制专门用于处理那些使用后必须正确关闭的资源(如 InputStreamOutputStream 等)的异常处理。

你可以在我写的这篇教程中了解更多:《Java 中的 Try-With-Resources》