CodeGym /课程 /JAVA 25 SELF /使用 try-with-resources:自动关闭资源

使用 try-with-resources:自动关闭资源

JAVA 25 SELF
第 36 级 , 课程 4
可用

1. 引言

在 Java 中(以及其他很多场景),与文件打交道就意味着在使用外部资源。当你打开一个文件时,操作系统会为你的程序分配一个“描述符”——一个特殊的标识符,使你可以读取和写入文件。这类描述符的数量是有限的:如果不关闭文件,程序可能会很快“吃掉”全部可用资源,并开始抛出类似 "Too many open files" 的神秘错误。

此外,如果文件未关闭,它可能会一直被其他程序锁定。比如,你以写方式打开了文件,忘了关闭,此时你和其他人都无法修改或删除它。这就像文件系统世界里的“永久劫持人质”。

生活中的例子

FileInputStream fis = new FileInputStream("data.txt");
int b = fis.read();
// ... 做了一些操作,然后忘了调用 fis.close()

如果不调用 close() 方法,文件会一直“挂着”,直到程序结束。在大型应用中,这会导致资源泄漏,甚至引发程序崩溃。

2. 旧方法:finally + close()

在 Java 7 之前,保证关闭文件的经典写法如下:

FileInputStream fis = null;
try {
    fis = new FileInputStream("data.txt");
    // 读取文件
    int b = fis.read();
    // ...
} catch (IOException e) {
    System.out.println("读取文件时出错:" + e.getMessage());
} finally {
    if (fis != null) {
        try {
            fis.close();
        } catch (IOException e) {
            System.out.println("关闭文件时出错:" + e.getMessage());
        }
    }
}

这种做法的缺点

  • 很容易忘记写 finally 块,从而导致资源泄漏。
  • 样板代码很多,尤其是有多个流时。
  • 关闭时若抛出异常,也需要额外捕获处理。
  • 代码臃肿、可读性下降。

3. 现代方法:try-with-resources

幸运的是,Java 7 引入了一个优雅且自动化的语法来解决这些问题 —— try-with-resources

它长什么样

try (FileInputStream fis = new FileInputStream("data.txt")) {
    int b = fis.read();
    // 操作文件
} catch (IOException e) {
    System.out.println("处理文件时出错:" + e.getMessage());
}
// 这里的 fis 已经被自动关闭!

关键点:所有在 try 后圆括号中声明的资源,会在代码块结束后自动关闭 —— 即使中途发生了异常。无需编写 finally,也不必单独捕获关闭时的错误 —— Java 会替你完成。

哪些类可以用在 try-with-resources 中?

任何实现了 AutoCloseable 接口(或较早的 Closeable)的类。这几乎涵盖了所有标准的 I/O 流:FileInputStreamFileOutputStreamBufferedReaderBufferedWriterScannerPrintWriter 等等。

4. try-with-resources 的语法:细节与示例

单个资源

try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"))) {
    String line = reader.readLine();
    System.out.println(line);
} catch (IOException e) {
    System.out.println("错误:" + e.getMessage());
}
// reader 已被自动关闭!

多个资源

可以用分号一次声明多个资源:

try (
    BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
    BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))
) {
    String line;
    while ((line = reader.readLine()) != null) {
        writer.write(line);
        writer.newLine();
    }
} catch (IOException e) {
    System.out.println("复制时出错:" + e.getMessage());
}
// 两个流都已关闭!

关闭顺序:资源会按照与声明相反的顺序关闭。会先调用 writer.close(),再调用 reader.close()。当一个流依赖另一个流时,这一点很重要。

与自定义类一起使用

如果你在编写自己的资源类,只需实现 AutoCloseable 接口:

class MyResource implements AutoCloseable {
    public void doSomething() {
        System.out.println("正在使用资源!");
    }

    @Override
    public void close() {
        System.out.println("资源已关闭!");
    }
}

try (MyResource res = new MyResource()) {
    res.doSomething();
}
// 退出代码块后将输出: "资源已关闭!"

5. 它如何工作:示意图

flowchart TD
    A[在 try-with-resources 中打开资源] --> B{try 块中是否发生异常?}
    B -- 否 --> C[资源自动关闭]
    B -- 是 --> D[资源自动关闭]
    D --> E[异常继续向外抛出]
    C --> F[程序继续执行]

结论:无论是否发生错误,资源都会被关闭!

6. 示例:把代码“改写成新风格”

之前(旧风格):

BufferedReader reader = null;
try {
    reader = new BufferedReader(new FileReader("input.txt"));
    String line = reader.readLine();
    System.out.println(line);
} catch (IOException e) {
    System.out.println("错误:" + e.getMessage());
} finally {
    if (reader != null) {
        try {
            reader.close();
        } catch (IOException e) {
            System.out.println("关闭时出错:" + e.getMessage());
        }
    }
}

之后(try-with-resources):

try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"))) {
    String line = reader.readLine();
    System.out.println(line);
} catch (IOException e) {
    System.out.println("错误:" + e.getMessage());
}
// 搞定,不需要 finally!

7. 关闭期间发生错误会怎样?

有时关闭资源本身也可能抛出异常(例如文件突然消失)。在 try-with-resources 中,这些异常不会丢失:如果在 try 块中已经有一个异常,而在关闭资源时又出现了第二个异常,它会作为被“抑制”的异常(suppressed exception)附加到主异常上。可以通过 Throwable.getSuppressed() 来查看。

示例

try (MyResource res = new MyResource()) {
    throw new IOException("try 块中的错误");
} catch (IOException e) {
    System.out.println("主错误:" + e.getMessage());
    for (Throwable suppressed : e.getSuppressed()) {
        System.out.println("被抑制的异常:" + suppressed.getMessage());
    }
}

8. 哪些类支持 try-with-resources?

很简单:任何实现了 AutoCloseable 接口的类。以下只是部分标准类:

用途
FileInputStream
从文件读取字节
FileOutputStream
向文件写入字节
FileReader/FileWriter
读取/写入文本
BufferedReader/Writer
对流进行缓冲
PrintWriter
写入带格式的文本
Scanner
从文件/控制台读取数据
ObjectInputStream/Output
序列化/反序列化
ZipInputStream/Output
处理 ZIP 压缩包
Socket, ServerSocket
网络连接

如果你在使用第三方库——请查看其文档:如果有 close() 方法,那么该类很可能支持 try-with-resources

9. 建议与有用的细节

可以在 try 之外声明变量(自 Java 9 起):如果资源是 final 或“有效 final(effectively final)”,可以复用已声明的资源:

BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
try (reader) {
    // ...
}

不止适用于文件:try-with-resources 适用于任何资源:网络连接、数据库、任何带有 close() 方法的对象。

不要忽视异常:即便使用了 try-with-resources,也别忘了捕获并处理异常 —— 它不是万能药,只是避免泄漏的便捷方式。

不要在 try 块内手动关闭资源:没有必要 —— Java 会替你做!如果手动调用了 close(),随后 try 块结束时还会再次尝试关闭已关闭的资源。通常这是安全的,但可能让人困惑。

10. 使用 try-with-resources 时的常见错误

错误 №1:干脆没用 try-with-resources。如果你还在写 finally { resource.close(); } —— 要么你停留在 2011 年,要么你还没读这篇讲座!请使用现代语法。

错误 №2:在 try 外声明资源,只在内部使用。这段代码不会自动关闭资源:

BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
try {
    // ... 使用 reader
} finally {
    // 这里却忘了关闭!
}

错误 №3:在 try 块内手动调用 close()。这不致命,但多此一举,还可能导致重复关闭。把它交给 Java 吧。

错误 №4:只捕获 Exception,忽略 IO 细节。最好捕获更具体的异常(FileNotFoundExceptionIOException),以便给用户更清晰的提示。

错误 №5:不处理被抑制的异常。如果在关闭资源时发生了错误,它可能被“抑制”。当你分析错误时,别忘了 getSuppressed()

1
调查/小测验
文件的读取与写入第 36 级,课程 4
不可用
文件的读取与写入
文件的读取与写入
评论 (1)
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION
ncksllpo 级别 44,Cherkasy,Ukraine
18 三月 2026
Scanner的用法