第47章 Java try-with-resources
自动资源管理 - 让资源释放更安全、更简洁
什么是try-with-resources
try-with-resources是Java 7引入的一个重要特性,它提供了一种自动管理资源的机制。通过这个特性,我们可以确保在try块执行完毕后,所有实现了AutoCloseable接口的资源都会被自动关闭,即使发生异常也不例外。
核心优势
- 自动资源管理:无需手动调用close()方法
- 异常安全:即使发生异常也能确保资源被正确释放
- 代码简洁:减少样板代码,提高代码可读性
- 防止资源泄漏:避免忘记关闭资源导致的内存泄漏
基本语法
try-with-resources基本语法
// 基本语法
try (ResourceType resource = new ResourceType()) {
// 使用资源的代码
} catch (Exception e) {
// 异常处理
}
// 多个资源
try (ResourceType1 resource1 = new ResourceType1();
ResourceType2 resource2 = new ResourceType2()) {
// 使用资源的代码
} catch (Exception e) {
// 异常处理
}
基础示例
TryWithResourcesBasic.java
import java.io.*;
import java.nio.file.*;
public class TryWithResourcesBasic {
public static void main(String[] args) {
// 示例1:文件读取
readFileExample();
// 示例2:文件写入
writeFileExample();
// 示例3:多个资源
copyFileExample();
}
// 使用try-with-resources读取文件
public static void readFileExample() {
System.out.println("=== 文件读取示例 ===");
try (BufferedReader reader = Files.newBufferedReader(
Paths.get("example.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.err.println("读取文件时发生错误: " + e.getMessage());
}
// reader会自动关闭,无需手动调用close()
}
// 使用try-with-resources写入文件
public static void writeFileExample() {
System.out.println("\n=== 文件写入示例 ===");
try (BufferedWriter writer = Files.newBufferedWriter(
Paths.get("output.txt"))) {
writer.write("Hello, try-with-resources!");
writer.newLine();
writer.write("这是第二行内容");
System.out.println("文件写入成功");
} catch (IOException e) {
System.err.println("写入文件时发生错误: " + e.getMessage());
}
// writer会自动关闭并刷新缓冲区
}
// 使用多个资源进行文件复制
public static void copyFileExample() {
System.out.println("\n=== 文件复制示例 ===");
try (BufferedReader reader = Files.newBufferedReader(
Paths.get("source.txt"));
BufferedWriter writer = Files.newBufferedWriter(
Paths.get("destination.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
}
System.out.println("文件复制成功");
} catch (IOException e) {
System.err.println("复制文件时发生错误: " + e.getMessage());
}
// 两个资源都会自动关闭
}
}
AutoCloseable接口
要在try-with-resources中使用的资源必须实现AutoCloseable接口。让我们看看如何创建自定义的可自动关闭资源:
CustomAutoCloseable.java
public class CustomAutoCloseable implements AutoCloseable {
private String resourceName;
private boolean isOpen;
public CustomAutoCloseable(String resourceName) {
this.resourceName = resourceName;
this.isOpen = true;
System.out.println("资源 [" + resourceName + "] 已打开");
}
public void doSomething() {
if (!isOpen) {
throw new IllegalStateException("资源已关闭");
}
System.out.println("使用资源 [" + resourceName + "] 执行操作");
}
@Override
public void close() throws Exception {
if (isOpen) {
isOpen = false;
System.out.println("资源 [" + resourceName + "] 已关闭");
// 模拟关闭过程中可能出现的异常
if ("error-resource".equals(resourceName)) {
throw new Exception("关闭资源时发生错误");
}
}
}
public boolean isOpen() {
return isOpen;
}
// 使用示例
public static void main(String[] args) {
System.out.println("=== 自定义AutoCloseable示例 ===");
// 正常使用
try (CustomAutoCloseable resource = new CustomAutoCloseable("normal-resource")) {
resource.doSomething();
} catch (Exception e) {
System.err.println("发生异常: " + e.getMessage());
}
System.out.println();
// 多个资源
try (CustomAutoCloseable resource1 = new CustomAutoCloseable("resource-1");
CustomAutoCloseable resource2 = new CustomAutoCloseable("resource-2")) {
resource1.doSomething();
resource2.doSomething();
} catch (Exception e) {
System.err.println("发生异常: " + e.getMessage());
}
System.out.println();
// 关闭时发生异常的情况
try (CustomAutoCloseable resource = new CustomAutoCloseable("error-resource")) {
resource.doSomething();
} catch (Exception e) {
System.err.println("发生异常: " + e.getMessage());
}
}
}
异常处理详解
try-with-resources在异常处理方面有一些特殊的行为,特别是当try块和close()方法都抛出异常时:
ExceptionHandlingExample.java
public class ExceptionHandlingExample {
// 模拟一个可能在使用和关闭时都抛出异常的资源
static class ProblematicResource implements AutoCloseable {
private String name;
public ProblematicResource(String name) {
this.name = name;
System.out.println("创建资源: " + name);
}
public void useResource() throws Exception {
System.out.println("使用资源: " + name);
if ("throw-in-use".equals(name)) {
throw new Exception("使用资源时发生异常");
}
}
@Override
public void close() throws Exception {
System.out.println("关闭资源: " + name);
if (name.contains("throw-in-close")) {
throw new Exception("关闭资源时发生异常");
}
}
}
public static void main(String[] args) {
// 示例1:只有try块抛出异常
System.out.println("=== 示例1:try块异常 ===");
try (ProblematicResource resource = new ProblematicResource("throw-in-use")) {
resource.useResource();
} catch (Exception e) {
System.out.println("捕获异常: " + e.getMessage());
// 检查被抑制的异常
Throwable[] suppressed = e.getSuppressed();
for (Throwable t : suppressed) {
System.out.println("被抑制的异常: " + t.getMessage());
}
}
System.out.println();
// 示例2:只有close()方法抛出异常
System.out.println("=== 示例2:close()异常 ===");
try (ProblematicResource resource = new ProblematicResource("throw-in-close")) {
resource.useResource();
} catch (Exception e) {
System.out.println("捕获异常: " + e.getMessage());
}
System.out.println();
// 示例3:try块和close()都抛出异常
System.out.println("=== 示例3:try和close都异常 ===");
try (ProblematicResource resource = new ProblematicResource("throw-in-use-and-close")) {
if (resource.name.contains("throw-in-use")) {
throw new Exception("try块中的异常");
}
resource.useResource();
} catch (Exception e) {
System.out.println("主异常: " + e.getMessage());
// try块的异常是主异常,close()的异常被抑制
Throwable[] suppressed = e.getSuppressed();
for (Throwable t : suppressed) {
System.out.println("被抑制的异常: " + t.getMessage());
}
}
System.out.println();
// 示例4:多个资源的异常处理
System.out.println("=== 示例4:多资源异常 ===");
try (ProblematicResource resource1 = new ProblematicResource("resource1-throw-in-close");
ProblematicResource resource2 = new ProblematicResource("resource2-throw-in-close")) {
resource1.useResource();
resource2.useResource();
} catch (Exception e) {
System.out.println("主异常: " + e.getMessage());
Throwable[] suppressed = e.getSuppressed();
for (int i = 0; i < suppressed.length; i++) {
System.out.println("被抑制的异常" + (i + 1) + ": " + suppressed[i].getMessage());
}
}
}
}
异常处理规则
- 主异常:try块中的异常是主异常
- 抑制异常:close()方法的异常会被添加到主异常的抑制异常列表中
- 多资源:资源按声明的相反顺序关闭
- 异常传播:即使close()抛出异常,所有资源仍会尝试关闭
与传统方式对比
特性 | 传统try-catch-finally | try-with-resources |
---|---|---|
代码长度 | 较长,需要显式的finally块 | 简洁,自动处理资源关闭 |
资源管理 | 手动调用close(),容易遗忘 | 自动调用close(),不会遗忘 |
异常处理 | 复杂,需要处理多个异常 | 简单,自动处理抑制异常 |
空指针检查 | 需要手动检查资源是否为null | 不需要,编译器保证 |
性能 | 相同 | 相同 |
传统方式
FileInputStream fis = null;
try {
fis = new FileInputStream("file.txt");
// 使用文件
} catch (IOException e) {
// 处理异常
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
// 处理关闭异常
}
}
}
try-with-resources
try (FileInputStream fis =
new FileInputStream("file.txt")) {
// 使用文件
} catch (IOException e) {
// 处理异常
}
// 文件自动关闭
最佳实践
使用建议
-
优先使用try-with-resources: 对于所有实现了AutoCloseable的资源,都应该使用try-with-resources
-
资源声明顺序: 按照依赖关系声明资源,被依赖的资源先声明
-
异常处理: 在catch块中处理业务异常,不需要特别处理资源关闭异常
-
自定义资源: 实现AutoCloseable时,确保close()方法是幂等的
-
性能考虑: 对于频繁创建的小资源,考虑使用资源池
注意事项
- Java版本:try-with-resources需要Java 7或更高版本
- 接口要求:资源必须实现AutoCloseable或Closeable接口
- final变量:在try-with-resources中声明的变量是隐式final的
- 异常抑制:了解异常抑制机制,避免丢失重要的异常信息
完整代码演示
点击下面的按钮,在在线IDE中查看和运行完整的try-with-resources示例代码:
常见问题
可以在try-with-resources中使用null吗?
不可以。如果资源初始化返回null,会在进入try块之前抛出NullPointerException。
资源的关闭顺序是什么?
资源按照声明的相反顺序关闭,即最后声明的资源最先关闭。
可以在try块外访问资源吗?
不可以。在try-with-resources中声明的资源只在try块内可见。
close()方法会被调用多次吗?
不会。每个资源的close()方法只会被调用一次,即使手动调用过。
扩展练习
练习建议
- 文件操作:使用try-with-resources实现文件的读取、写入和复制功能
- 数据库连接:创建数据库连接管理类,使用try-with-resources确保连接正确关闭
- 自定义资源:实现一个自定义的AutoCloseable资源,模拟网络连接或其他系统资源
- 异常处理:测试各种异常情况,观察异常抑制机制的工作原理
- 性能测试:比较try-with-resources和传统方式的性能差异