第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示例代码:

💻 查看完整代码 - 在线IDE体验

常见问题

可以在try-with-resources中使用null吗?

不可以。如果资源初始化返回null,会在进入try块之前抛出NullPointerException。

资源的关闭顺序是什么?

资源按照声明的相反顺序关闭,即最后声明的资源最先关闭。

可以在try块外访问资源吗?

不可以。在try-with-resources中声明的资源只在try块内可见。

close()方法会被调用多次吗?

不会。每个资源的close()方法只会被调用一次,即使手动调用过。

扩展练习

练习建议

  1. 文件操作:使用try-with-resources实现文件的读取、写入和复制功能
  2. 数据库连接:创建数据库连接管理类,使用try-with-resources确保连接正确关闭
  3. 自定义资源:实现一个自定义的AutoCloseable资源,模拟网络连接或其他系统资源
  4. 异常处理:测试各种异常情况,观察异常抑制机制的工作原理
  5. 性能测试:比较try-with-resources和传统方式的性能差异