Java try-with-resources

try-with-resources 是 Java 7 引入的語法,用於自動關閉實作 AutoCloseable 介面的資源,避免資源洩漏。

傳統方式的問題

傳統使用 finally 關閉資源的方式容易出錯:

// 傳統方式:繁瑣且容易出錯
BufferedReader reader = null;
try {
    reader = new BufferedReader(new FileReader("file.txt"));
    String line = reader.readLine();
    System.out.println(line);
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (reader != null) {
        try {
            reader.close();  // close() 也可能拋出例外
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

try-with-resources 語法

// 簡潔且安全
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
    String line = reader.readLine();
    System.out.println(line);
} catch (IOException e) {
    e.printStackTrace();
}
// reader 會自動關閉

基本用法

單一資源

try (FileInputStream fis = new FileInputStream("input.txt")) {
    int data;
    while ((data = fis.read()) != -1) {
        System.out.print((char) data);
    }
}

多個資源

使用分號分隔,資源按相反順序關閉:

try (FileInputStream fis = new FileInputStream("input.txt");
     FileOutputStream fos = new FileOutputStream("output.txt")) {

    int data;
    while ((data = fis.read()) != -1) {
        fos.write(data);
    }
}
// 關閉順序:fos → fis

Java 9+ 簡化語法

如果資源已經是 final 或 effectively final,可以在 try 外部宣告:

// Java 9+
BufferedReader reader = new BufferedReader(new FileReader("file.txt"));
try (reader) {  // 不需要重新宣告
    System.out.println(reader.readLine());
}
// 多個資源
FileInputStream fis = new FileInputStream("input.txt");
FileOutputStream fos = new FileOutputStream("output.txt");
try (fis; fos) {
    // 使用資源
}

AutoCloseable 介面

只有實作 AutoCloseableCloseable 的類別才能使用:

public interface AutoCloseable {
    void close() throws Exception;
}

public interface Closeable extends AutoCloseable {
    void close() throws IOException;
}

常見可自動關閉的類別

  • 所有 I/O 流:InputStreamOutputStreamReaderWriter
  • 資料庫連線:ConnectionStatementResultSet
  • 網路資源:SocketServerSocket
  • 其他:ScannerFormatterZipFile

自訂 AutoCloseable

public class MyResource implements AutoCloseable {
    private String name;

    public MyResource(String name) {
        this.name = name;
        System.out.println(name + " 開啟");
    }

    public void use() {
        System.out.println(name + " 使用中");
    }

    @Override
    public void close() {
        System.out.println(name + " 關閉");
    }
}

// 使用
try (MyResource res = new MyResource("資源1")) {
    res.use();
}
// 輸出:
// 資源1 開啟
// 資源1 使用中
// 資源1 關閉

多個自訂資源

try (MyResource res1 = new MyResource("資源1");
     MyResource res2 = new MyResource("資源2")) {
    res1.use();
    res2.use();
}
// 輸出:
// 資源1 開啟
// 資源2 開啟
// 資源1 使用中
// 資源2 使用中
// 資源2 關閉  ← 後開先關
// 資源1 關閉

例外處理

close() 拋出的例外

如果 try 區塊和 close() 都拋出例外,close() 的例外會被抑制:

public class FailingResource implements AutoCloseable {
    @Override
    public void close() throws Exception {
        throw new Exception("關閉時發生錯誤");
    }
}

try (FailingResource res = new FailingResource()) {
    throw new Exception("主程式錯誤");
} catch (Exception e) {
    System.out.println("主例外:" + e.getMessage());

    // 取得被抑制的例外
    Throwable[] suppressed = e.getSuppressed();
    for (Throwable t : suppressed) {
        System.out.println("被抑制的例外:" + t.getMessage());
    }
}
// 輸出:
// 主例外:主程式錯誤
// 被抑制的例外:關閉時發生錯誤

只有 close() 拋出例外

try (FailingResource res = new FailingResource()) {
    System.out.println("使用資源");
} catch (Exception e) {
    System.out.println("例外:" + e.getMessage());
}
// 輸出:
// 使用資源
// 例外:關閉時發生錯誤

實際範例

讀取檔案

public String readFile(String path) throws IOException {
    StringBuilder content = new StringBuilder();
    try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
        String line;
        while ((line = reader.readLine()) != null) {
            content.append(line).append("\n");
        }
    }
    return content.toString();
}

寫入檔案

public void writeFile(String path, String content) throws IOException {
    try (BufferedWriter writer = new BufferedWriter(new FileWriter(path))) {
        writer.write(content);
    }
}

複製檔案

public void copyFile(String source, String target) throws IOException {
    try (InputStream in = new FileInputStream(source);
         OutputStream out = new FileOutputStream(target)) {

        byte[] buffer = new byte[1024];
        int length;
        while ((length = in.read(buffer)) > 0) {
            out.write(buffer, 0, length);
        }
    }
}

資料庫操作

public List<User> findAllUsers() throws SQLException {
    List<User> users = new ArrayList<>();
    String sql = "SELECT id, name, email FROM users";

    try (Connection conn = dataSource.getConnection();
         Statement stmt = conn.createStatement();
         ResultSet rs = stmt.executeQuery(sql)) {

        while (rs.next()) {
            User user = new User();
            user.setId(rs.getInt("id"));
            user.setName(rs.getString("name"));
            user.setEmail(rs.getString("email"));
            users.add(user);
        }
    }
    return users;
}

使用 PreparedStatement

public User findById(int id) throws SQLException {
    String sql = "SELECT * FROM users WHERE id = ?";

    try (Connection conn = dataSource.getConnection();
         PreparedStatement pstmt = conn.prepareStatement(sql)) {

        pstmt.setInt(1, id);

        try (ResultSet rs = pstmt.executeQuery()) {
            if (rs.next()) {
                return new User(
                    rs.getInt("id"),
                    rs.getString("name"),
                    rs.getString("email")
                );
            }
        }
    }
    return null;
}

網路連線

public String fetchUrl(String urlString) throws IOException {
    URL url = new URL(urlString);
    try (BufferedReader reader = new BufferedReader(
            new InputStreamReader(url.openStream()))) {

        StringBuilder result = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            result.append(line);
        }
        return result.toString();
    }
}

比較

特性try-finallytry-with-resources
程式碼量
可讀性較差較好
關閉順序手動控制自動(後開先關)
例外處理可能遺失例外抑制例外被保留
錯誤風險容易忘記關閉自動關閉

重點整理

  • 使用 try-with-resources 自動關閉資源
  • 資源必須實作 AutoCloseable 介面
  • 多個資源用分號分隔,後開先關
  • Java 9+ 可以使用外部宣告的變數
  • close() 的例外會被抑制,可用 getSuppressed() 取得
  • 適用於檔案、資料庫、網路等需要關閉的資源