Java try-with-resources 文

Java 9 新機能

try-with-resources は Java 7 で導入された簡潔なリソース管理方式であり、使用後に自動的に閉じる必要があるリソース(ファイル、データベース接続、ネットワーク接続など)に適しています。

try-with-resources は try-catch 文ブロック内で使用するリソースを簡単に閉じることができます。いわゆるリソース(resource)とは、プログラムの完了後に閉じる必要があるオブジェクトを指します。

try-with-resources 文は、各リソースが文の終了時に閉じられることを保証します。

java.lang.AutoCloseable インターフェース(java.io.Closeable を実装したすべてのオブジェクトを含む)を実装したすべてのオブジェクトは、リソースとして使用できます。

利点:

  • コードの簡素化:リソースを手動で閉じるコードが不要になり、ロジックがより明確になります。
  • エラーの削減:リソースのクローズを自動処理し、リソースの閉じ忘れやfinallyブロック内で発生する例外の処理漏れを防ぎます。
  • パフォーマンスの向上:リソースリークを減らし、システムリソースを節約します。

実行フロー

try-with-resources を理解する核心は、リソースのライフサイクルと、例外発生時の制御フローの流れを把握することにあります。

基本構文

try-with-resources の構文は次のとおりです:

try (ResourceType resource = new ResourceType()) {
    // 使用资源
} catch (ExceptionType e) {
    // 处理异常
}

try ブロック内で宣言されたリソースは、コードの実行完了後に自動的に閉じられ、例外が発生した場合でも安全に閉じられます。

複数リソース管理(宣言とは逆順でクローズ):

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

    while (rs.next()) {
        System.out.println(rs.getString(1));
    }
} catch (SQLException e) {
    e.printStackTrace();
}

クローズ順:

rs → stmt → conn(声明顺序的逆序)

クローズ順:複数のリソースは宣言順の逆順で閉じられます。つまり、後で宣言されたリソースが最初に閉じられます。これはスタック式(LIFO)管理と一致し、依存関係のあるリソースが正しく解放されることを保証します。

宣言フェーズ 宣言 / 初期化 res1 Connection ① res2 Statement ② res3 ResultSet try ブロックの実行 実行 try { ... } クローズフェーズ 自動クローズ(逆順) close res3 ResultSet close res2 Statement close res1 Connection クローズ矢印 ③ ④ ⑤ try ブロックからクローズ矢印へ try ブロック終了

AutoCloseable インターフェースの使用

try-with-resources で管理したい任意のカスタムクラスは、java.lang.AutoCloseable(または java.io.Closeable)インターフェースを実装し、その close() メソッドを実装するだけで済みます。

例

public class DatabaseConnection implements AutoCloseable {
    private final String url;

    public DatabaseConnection(String url) {
        this.url = url;
        System.out.println("接続が確立されました: " + url);
    }

    public void query(String sql) {
        System.out.println("クエリを実行: " + sql);
    }

    @Override
    public void close() {
        System.out.println("接続が閉じられました: " + url);
        // 基盤となる接続リソースを解放...
    }
}

// 使用
try (DatabaseConnection db = new DatabaseConnection("jdbc:mysql://localhost/mydb")) {
    db.query("SELECT * FROM users");
} catch (Exception e) {
    e.printStackTrace();
}

出力:

连接已建立: jdbc:mysql://localhost/mydb
执行查询: SELECT * FROM users
连接已关闭: jdbc:mysql://localhost/mydb

例外処理と抑制例外

try ブロック内のビジネスロジックと close() が同時に例外をスローした場合、try-with-resources は特別な戦略を採用します。ビジネス例外は主例外として上位に伝播し、close() で発生した例外は抑制(Suppressed)され、主例外に付加されます。getSuppressed() で取得できます。

try ブロック try { ... } 例外 A をスロー (ビジネス例外) close() 例外 B をスロー (クローズ例外) 線 try → close トリガー 主例外 主例外 Exception A 抑制例外 抑制例外 Exception B 矢印 suppressed を付加 addSuppressed() catch ブロック catch (Exception e) 主例外 A をキャッチ e.getSuppressed() 抑制されたものを取得 Exception B catch に接続

例

try (ResourceWithException res = new ResourceWithException()) {
    res.doSomething();           // 例外 A をスロー(ビジネス例外)
} catch (Exception e) {
    System.out.println("主例外: "  + e.getMessage());
    for (Throwable s : e.getSuppressed()) {   // 例外 B を取得
        System.out.println("抑制例外: " + s.getMessage());
    }
}

出力:

主异常: Exception in doSomething
抑制异常: Exception in close

注意:従来の try-finally では、finally ブロックの例外がビジネス例外を上書きし、元のエラーが失われる原因となります。try-with-resources は抑制例外メカニズムによってこの古典的な落とし穴を解決しました。

Java 9 拡張:外部変数の再利用

Java 9 より前では、try-with-resources はリソースを try() 内で再宣言する必要がありました。

Java 9 でこの制限が緩和されました。変数が effectively final(事実上の定数)であれば、try() 内で直接参照でき、再宣言は不要です。

Java 7 / 8 — 再宣言が必要

BufferedReader br = new BufferedReader(new StringReader("hello"));
try (BufferedReader br1 = br) {    // ← 新しい変数 br1 を宣言する必要がある
    System.out.println(br1.readLine());
}

Java 9+ — 直接再利用

BufferedReader br = new BufferedReader(new StringReader("hello"));
try (br) {                           // ← br を直接使用、再宣言不要
    System.out.println(br.readLine());
}

effectively final:変数は初期化後に再度代入されていない場合、コンパイラは自動的に final とみなします。final キーワードを明示的に書く必要はありません。

try-finally との比較

観点 try-finally try-with-resources
コード量 多い——close() の手動呼び出しが必要 少ない——自動クローズ
close 漏れのリスク 高い——人的ミスでリソースリークが発生しやすい ゼロ——コンパイラが保証
複数リソースのネスト ネストが深く、可読性が低い セミコロン区切りで、フラットで明確
例外処理 finally の例外がビジネス例外を上書き 抑制例外メカニズムで、情報が完全に保持される
適用範囲 任意のクリーンアップロジック AutoCloseable を実装したリソース

try-finally の書き方(冗長でエラーが発生しやすい)

BufferedReader br = null;
try {
    br = new BufferedReader(new FileReader("data.txt"));
    String line;
    while ((line = br.readLine()) != null) System.out.println(line);
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (br != null) {               // さらに null チェックも必要
        try { br.close(); }        // close 自体も例外をスローする可能性がある
        catch (IOException e) { e.printStackTrace(); }
    }
}

try-with-resources の書き方(簡潔で安全)

try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
    String line;
    while ((line = br.readLine()) != null) System.out.println(line);
} catch (IOException e) {
    e.printStackTrace();
}

一般的な使用シーン

分類 一般的なクラス/インターフェース
📁 ファイル I/O FileInputStream、FileOutputStream、BufferedReader、BufferedWriter、FileChannel
🗄 データベース Connection、PreparedStatement、Statement、ResultSet、EntityManager
🌐 ネットワーク通信 Socket、ServerSocket、HttpURLConnection、InputStream、OutputStream

明示的にクローズが必要なリソースを扱う場合は、try-finally ではなく try-with-resources を優先的に使用すべきです。コンパイラがリソースのクローズを保証し、コードがより簡潔で、例外情報がより完全になります。これは Java リソース管理のベストプラクティスです。

Java 9 新機能

その他の拡張