try-with-resourcesとは、try (リソースの宣言) { ... } と書くだけで、ブロックを抜けるときにリソースの close() を自動で呼んでくれる Java 7 からの構文です。 対象は AutoCloseable を実装したクラスで、ファイル、DB接続、ストリームなどを、正常終了でも例外発生でも確実に閉じられます。

この記事では、AutoCloseable を自作して「閉じる順序」「抑制例外(getSuppressed)」「finally で例外が消える現象」を OpenJDK 21.0.10 で実際に再現し、出力をそのまま掲載しています。Java 9 の try (r) 構文、BufferedReader・Files.lines・JDBC のコピペ用コード、Closeable との違いまで、1本でたどれるようにまとめました。Java 25 での扱いは公式ドキュメントで確認して注記しています。

try-with-resourcesの早見表

やりたいこと 書き方 ポイント
リソースを1つ使う try (var r = Files.newBufferedReader(path)) { ... } ブロックを抜けると r.close() が呼ばれる
複数のリソースを使う try (A a = ...; B b = ...) { ... } ; で区切る。閉じるのは宣言の逆順
既存の変数を置く(Java 9〜) try (reader) { ... } 変数が final か、実質的に final であること
例外を処理する try (...) { ... } catch (IOException e) { ... } catch より先に close が済んでいる
後始末を足す try (...) { ... } finally { ... } finally は close のあとに実行される
close の例外を調べる e.getSuppressed() 本体と close の両方で例外が出たとき、close 側が入る
自作クラスを対象にする class X implements AutoCloseable close() を実装するだけ
入出力系の自作クラス class X implements Closeable close() の検査例外は IOException
比べる点 try-finally で close try-with-resources
close の呼び忘れ 自分で書く必要がある 構文が自動で呼ぶ
null チェック if (r != null) を書く 不要(null なら close を呼ばない)
複数リソース finally が入れ子になる ; で並べるだけ。逆順に閉じる
本体と close の両方で例外 close の例外が残り、本体の例外が消える 本体の例外が残り、close の例外は抑制例外として保持
変数のスコープ try の外で宣言する try ブロックの中だけ
使える版 すべて Java 7 以降(try (r) は Java 9 以降)

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Java try-with-resourcesの使い方|finallyとの違い」

基本の書き方:finallyとの比較

try-with-resources は、try の直後の丸かっこにリソースを宣言するだけです。同じ「ファイルの1行目を読む」処理を、2つの書き方で比べます。

// try-finally で閉じる書き方
static String firstLineOld(Path path) throws IOException {
    BufferedReader reader = null;
    try {
        reader = Files.newBufferedReader(path);
        return reader.readLine();
    } finally {
        if (reader != null) {
            reader.close();
        }
    }
}

// try-with-resources
static String firstLine(Path path) throws IOException {
    try (BufferedReader reader = Files.newBufferedReader(path)) {
        return reader.readLine();
    }
}

変数の宣言・null チェック・close の呼び出しが、丸かっこ1つにまとまります。 catch と finally は必要なときだけ付ければよく、どちらも省略できます。

closeの順序は宣言の逆順(自作AutoCloseableで確認)

複数のリソースは、宣言した順に開かれ、逆の順に閉じられます。 AutoCloseable を実装した自作クラスで、開く・使う・閉じるの順番を表示します。

public class CloseOrder {
    // 自作のリソース:AutoCloseable を実装するだけで try-with-resources に置ける
    static class Res implements AutoCloseable {
        private final String name;
        Res(String name) { this.name = name; System.out.println("open  " + name); }
        void use() { System.out.println("use   " + name); }
        @Override public void close() { System.out.println("close " + name); }
    }

    public static void main(String[] args) {
        try (Res a = new Res("A"); Res b = new Res("B"); Res c = new Res("C")) {
            a.use(); b.use(); c.use();
            throw new IllegalStateException("本体で例外");
        } catch (IllegalStateException e) {
            System.out.println("catch " + e.getMessage());
        } finally {
            System.out.println("finally");
        }
    }
}

実行結果(java CloseOrder.java、OpenJDK 21.0.10):

open  A
open  B
open  C
use   A
use   B
use   C
close C
close B
close A
catch 本体で例外
finally

読み取れることは3つです。

  • 閉じる順序は C → B → A(宣言の逆順)。あとから開いたものが先に開いたものに依存していても、安全に閉じられます。
  • catch に入る時点で、すべてのリソースは閉じ終わっています。 catch の中でリソースを使うことはできません。
  • finally はいちばん最後です。順番は「本体 → close(逆順) → catch → finally」になります。
flowchart LR
    A["リソースを開く<br>A → B → C"] --> B["try 本体"]
    B --> C["close<br>C → B → A"]
    C --> D["catch"]
    D --> E["finally"]

抑制例外getSuppressedを再現する

本体と close の両方で例外が発生したとき、呼び出し元に届くのは本体の例外です。close の例外は消えずに「抑制例外(suppressed exception)」として本体の例外に添付され、getSuppressed() で取り出せます。

public class Suppressed {
    static class Res implements AutoCloseable {
        private final String name;
        Res(String name) { this.name = name; }
        @Override public void close() {
            throw new IllegalStateException("close失敗 " + name);
        }
    }

    public static void main(String[] args) {
        try (Res a = new Res("A"); Res b = new Res("B")) {
            throw new RuntimeException("本体の例外");
        } catch (Exception e) {
            System.out.println("届いた例外 : " + e.getMessage());
            for (Throwable s : e.getSuppressed()) {
                System.out.println("抑制例外   : " + s.getMessage());
            }
        }

        // 本体が正常終了し、close だけが例外を投げた場合
        try (Res c = new Res("C")) {
            System.out.println("本体は正常終了");
        } catch (Exception e) {
            System.out.println("届いた例外 : " + e.getMessage() + " / 抑制例外の数 = " + e.getSuppressed().length);
        }
    }
}

実行結果(java Suppressed.java、OpenJDK 21.0.10):

届いた例外 : 本体の例外
抑制例外   : close失敗 B
抑制例外   : close失敗 A
本体は正常終了
届いた例外 : close失敗 C / 抑制例外の数 = 0
本体 close 呼び出し元に届く例外 getSuppressed()
正常終了 正常終了 なし -
例外 正常終了 本体の例外 空
正常終了 例外 close の例外 空
例外 例外 本体の例外 close の例外(閉じた順)

本体が正常に終わったときは、close の例外がそのまま届きます(後半の出力)。書き込み系のリソースでは、close の時点でバッファの内容が書き出されるため、close の例外も大切な情報です。

スタックトレースには、抑制例外が Suppressed: という見出しで表示されます。catch せずに終了させた場合の出力です。

public class Trace {
    static class Res implements AutoCloseable {
        @Override public void close() { throw new IllegalStateException("close失敗"); }
    }
    public static void main(String[] args) {
        try (Res r = new Res()) {
            throw new RuntimeException("本体の例外");
        }
    }
}

実行結果(java Trace.java、OpenJDK 21.0.10。起動用クラスの行は省略):

Exception in thread "main" java.lang.RuntimeException: 本体の例外
    at Trace.main(Trace.java:7)
    Suppressed: java.lang.IllegalStateException: close失敗
        at Trace$Res.close(Trace.java:3)
        at Trace.main(Trace.java:6)

ログ出力ライブラリに例外オブジェクトを渡せば、この Suppressed: の部分も一緒に記録されます。e.getMessage() だけを出力すると抑制例外は見えなくなるので、例外オブジェクトごと渡すのがおすすめです。

finallyで例外が消える現象を再現する

finally の中で close が例外を投げると、本体で起きた例外は上書きされて見えなくなります。 同じ状況を、try-finally と try-with-resources で比べます。

public class FinallyLost {
    static class Res implements AutoCloseable {
        @Override public void close() { throw new IllegalStateException("close失敗"); }
    }

    // finally で close する書き方
    static void withFinally() {
        Res r = new Res();
        try {
            throw new RuntimeException("本体の例外(本当の原因)");
        } finally {
            r.close();   // ここで投げた例外が、本体の例外を上書きする
        }
    }

    // try-with-resources
    static void withResources() {
        try (Res r = new Res()) {
            throw new RuntimeException("本体の例外(本当の原因)");
        }
    }

    @SuppressWarnings("finally")
    static String returnInFinally() {
        try {
            throw new RuntimeException("本体の例外");
        } finally {
            return "finally の return";   // 例外そのものが消える
        }
    }

    public static void main(String[] args) {
        try { withFinally(); } catch (Exception e) {
            System.out.println("finally            : " + e.getMessage() + " / 抑制 = " + e.getSuppressed().length);
        }
        try { withResources(); } catch (Exception e) {
            System.out.println("try-with-resources : " + e.getMessage() + " / 抑制 = " + e.getSuppressed()[0].getMessage());
        }
        System.out.println("finally で return  : " + returnInFinally());
    }
}

実行結果(java FinallyLost.java、OpenJDK 21.0.10):

finally            : close失敗 / 抑制 = 0
try-with-resources : 本体の例外(本当の原因) / 抑制 = close失敗
finally で return  : finally の return
書き方 届いた例外 本体の例外(本当の原因)
finally で close close失敗 どこにも残らない
try-with-resources 本体の例外 そのまま届く。close失敗は抑制例外に残る
finally で return 例外なし(戻り値が返る) どこにも残らない

finally で close する書き方では、調査の手がかりになる「最初に起きた例外」がログに残りません。try-with-resources に置き換えると、最初の例外と close の例外の両方が残るので、原因をたどりやすくなります。3行目は、finally の中に return を書くと例外そのものがなくなる例です。finally には後始末だけを書き、return や throw は置かない形にすると安心です。

コンパイラが展開するイメージ

try-with-resources は、コンパイル時におおよそ次のような形に展開されます(Java言語仕様 14.20.3 の説明をもとにした概念図で、実行したコードではありません)。

Res r = new Res();
Throwable primary = null;
try {
    // 本体
} catch (Throwable t) {
    primary = t;
    throw t;
} finally {
    if (r != null) {
        if (primary != null) {
            try { r.close(); } catch (Throwable c) { primary.addSuppressed(c); }
        } else {
            r.close();
        }
    }
}

null チェックと addSuppressed まで含めて自動で書いてくれるのが、この構文の価値です。

Java 9のtry (r) 構文:既存の変数をそのまま置く

Java 9 からは、すでに宣言済みの変数を try (変数名) と書いて対象にできます。 メソッドの引数で受け取ったリソースを閉じたいときに便利です。Java 8 までは try (BufferedReader r2 = reader) のように、別名の変数を宣言し直す必要がありました。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;

public class Java9 {
    // 引数で受け取ったリソースを、その変数名のまま try に置ける(Java 9 以降)
    static String firstLine(BufferedReader reader) throws IOException {
        try (reader) {
            return reader.readLine();
        }
    }

    public static void main(String[] args) throws IOException {
        BufferedReader r1 = new BufferedReader(new StringReader("1行目\n2行目"));
        System.out.println(firstLine(r1));
        try {
            r1.readLine();
        } catch (IOException e) {
            System.out.println("閉じたあとに読むと: " + e);
        }

        // 複数並べる・新しい宣言と混ぜることもできる
        final BufferedReader r2 = new BufferedReader(new StringReader("A"));
        BufferedReader r3 = new BufferedReader(new StringReader("B"));
        try (r2; r3; BufferedReader r4 = new BufferedReader(new StringReader("C"))) {
            System.out.println(r2.readLine() + r3.readLine() + r4.readLine());
        }
    }
}

実行結果(java Java9.java、OpenJDK 21.0.10):

1行目
閉じたあとに読むと: java.io.IOException: Stream closed
ABC

条件は、変数が final か「実質的に final」(一度も再代入していない)であることです。再代入している変数を置くと、コンパイルエラーになります。

import java.io.BufferedReader;
import java.io.StringReader;

public class NotFinal {
    public static void main(String[] args) throws Exception {
        BufferedReader r = new BufferedReader(new StringReader("A"));
        r = new BufferedReader(new StringReader("B"));   // 再代入している
        try (r) {
            System.out.println(r.readLine());
        }
    }
}

コンパイル結果(javac NotFinal.java、OpenJDK 21.0.10):

NotFinal.java:8: error: variable r used as a try-with-resources resource neither final nor effectively final
        try (r) {
             ^
1 error

try (r) の形では、変数が try の外でも見えるため、閉じたあとにうっかり使える点に気をつけます(上の実行結果の2行目)。新しく開くリソースは、これまでどおり丸かっこの中で宣言するのが基本です。

リソースがnullのとき・生成に失敗したとき

リソースが null なら close は呼ばれず、途中の生成で例外が出たら、そこまでに開いたものだけが閉じられます。

public class NullAndInit {
    static class Res implements AutoCloseable {
        private final String name;
        Res(String name) {
            if (name.equals("B")) throw new IllegalArgumentException("B の生成に失敗");
            this.name = name;
            System.out.println("open  " + name);
        }
        @Override public void close() { System.out.println("close " + name); }
    }

    public static void main(String[] args) {
        // 1. リソースが null のときは close を呼ばない(NullPointerException にならない)
        try (Res r = null) {
            System.out.println("r = " + r);
        }

        // 2. 2つ目の生成で例外:生成済みの A は閉じられ、本体は実行されない
        try (Res a = new Res("A"); Res b = new Res("B")) {
            System.out.println("本体");
        } catch (IllegalArgumentException e) {
            System.out.println("catch " + e.getMessage());
        }
    }
}

実行結果(java NullAndInit.java、OpenJDK 21.0.10):

r = null
open  A
close A
catch B の生成に失敗

ラップするリソースは変数を分けて宣言する

new Wrapper(new Inner()) のように入れ子で1行に書くと、外側の生成で例外が出たときに、内側のリソースを閉じる変数がありません。 変数を分けて宣言すると、内側も確実に閉じられます。

public class Nested {
    static class Inner implements AutoCloseable {
        @Override public void close() { System.out.println("close Inner"); }
    }
    static class Wrapper implements AutoCloseable {
        private final Inner inner;
        Wrapper(Inner inner, boolean fail) {
            if (fail) throw new IllegalStateException("Wrapper の生成に失敗");
            this.inner = inner;
        }
        @Override public void close() { System.out.println("close Wrapper"); inner.close(); }
    }

    public static void main(String[] args) {
        System.out.println("--- 1行にまとめて書いた場合");
        try (Wrapper w = new Wrapper(new Inner(), true)) {
            System.out.println("本体");
        } catch (IllegalStateException e) {
            System.out.println("catch " + e.getMessage());
        }

        System.out.println("--- 変数を分けて書いた場合");
        try (Inner in = new Inner(); Wrapper w = new Wrapper(in, true)) {
            System.out.println("本体");
        } catch (IllegalStateException e) {
            System.out.println("catch " + e.getMessage());
        }
    }
}

実行結果(java Nested.java、OpenJDK 21.0.10):

--- 1行にまとめて書いた場合
catch Wrapper の生成に失敗
--- 変数を分けて書いた場合
close Inner
catch Wrapper の生成に失敗

1行にまとめた場合は close Inner が表示されず、Inner が開いたままになっています。new BufferedReader(new InputStreamReader(new FileInputStream(...))) のような組み立てでも考え方は同じで、土台になるリソースを先に単独の変数で宣言しておくと安心です。

実例1:BufferedReader・BufferedWriter・Files.lines

ファイルの読み書きは、try-with-resources の代表的な使いどころです。 Files.lines が返す Stream もファイルを開いたままにしているため、同じように閉じます。

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;

public class IoDemo {
    public static void main(String[] args) throws IOException {
        Path path = Path.of("tasks.txt");

        // 書き込み:BufferedWriter
        try (BufferedWriter w = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) {
            w.write("資料作成,完了\n");
            w.write("レビュー,未完了\n");
            w.write("リリース,未完了\n");
        }

        // 読み込み:BufferedReader で1行ずつ
        try (BufferedReader r = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
            String line;
            while ((line = r.readLine()) != null) {
                System.out.println("read : " + line);
            }
        }

        // 読み込み:Files.lines(Stream もファイルを開いているので閉じる)
        try (Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8)
                .onClose(() -> System.out.println("Stream を閉じました"))) {
            long open = lines.filter(s -> s.endsWith("未完了")).count();
            System.out.println("未完了 = " + open);
        }
        Files.delete(path);
    }
}

実行結果(java IoDemo.java、OpenJDK 21.0.10):

read : 資料作成,完了
read : レビュー,未完了
read : リリース,未完了
未完了 = 2
Stream を閉じました
API try-with-resources 理由
Files.newBufferedReader / newBufferedWriter 必要 ファイルを開いたまま返す
Files.lines / list / walk / find 必要 Stream がファイルやフォルダを開いている
Files.readString / readAllLines / writeString 不要 メソッドの中で開いて閉じる
list.stream() などコレクションの Stream 不要 閉じるべき資源を持たない
Scanner・PrintWriter・InputStream・OutputStream 必要 いずれも Closeable

実例2:JDBCのConnection・PreparedStatement・ResultSet

JDBC では、Connection・PreparedStatement・ResultSet の3つがすべて AutoCloseable です。 開いた順(Connection → PreparedStatement → ResultSet)の逆順に閉じる必要がありますが、try-with-resources なら宣言順に並べるだけで済みます。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class JdbcDemo {
    static final String URL = "jdbc:postgresql://localhost:5432/t140";

    public static void main(String[] args) throws SQLException {
        String sql = "SELECT id, name, stock FROM items WHERE stock >= ? ORDER BY id";

        // Connection → PreparedStatement の順に宣言。閉じるのは逆順
        try (Connection con = DriverManager.getConnection(URL, "t140", System.getenv("DB_PASSWORD"));
             PreparedStatement ps = con.prepareStatement(sql)) {

            try (Statement st = con.createStatement()) {
                st.execute("CREATE TABLE IF NOT EXISTS items (id serial PRIMARY KEY, name text, stock int)");
                st.execute("TRUNCATE items RESTART IDENTITY");
                st.executeUpdate("INSERT INTO items (name, stock) VALUES ('ノート', 12), ('ペン', 0), ('付箋', 30)");
            }

            ps.setInt(1, 1);   // パラメータを設定してから ResultSet 用の try を開く
            try (ResultSet rs = ps.executeQuery()) {
                while (rs.next()) {
                    System.out.println(rs.getInt("id") + " " + rs.getString("name") + " " + rs.getInt("stock"));
                }
            }
        }
    }
}

実行結果(PostgreSQL 16 とJDBCドライバ 42.7.2 で java -cp postgresql.jar JdbcDemo.java、OpenJDK 21.0.10):

1 ノート 12
3 付箋 30

ps.setInt(...) のようなパラメータ設定は丸かっこの中に書けないため、ResultSet は内側にもう1つ try を作って宣言します。 この形が JDBC の定番です。接続プールを使っている場合も書き方は同じで、Connection の close() が「プールへ返す」動作になります。

実例3:ExecutorServiceとロック解放

Java 19 以降は ExecutorService も AutoCloseable になり、try-with-resources に置けます。 close() は投入済みのタスクがすべて終わるまで待ってから戻ります。また、AutoCloseable はメソッドが1つだけのインターフェースなので、ラムダ式やメソッド参照を「ブロックを抜けるときの後始末」として渡すこともできます。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;

public class ExecDemo {
    public static void main(String[] args) {
        // ExecutorService は Java 19 から AutoCloseable(close は全タスクの完了を待つ)
        try (ExecutorService ex = Executors.newFixedThreadPool(2)) {
            for (int i = 1; i <= 3; i++) {
                int n = i;
                ex.submit(() -> System.out.println("task " + n));
            }
        }
        System.out.println("全タスク完了後にここへ来る");

        // AutoCloseable は関数型インターフェースとしても使える:ロックの解放をまとめる例
        ReentrantLock lock = new ReentrantLock();
        lock.lock();
        try (AutoCloseable unlock = lock::unlock) {
            System.out.println("locked = " + lock.isLocked());
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
        System.out.println("locked = " + lock.isLocked());
    }
}

実行結果(java ExecDemo.java、OpenJDK 21.0.10。task の表示順は実行ごとに変わることがあります):

task 1
task 2
task 3
全タスク完了後にここへ来る
locked = true
locked = false

AutoCloseableとCloseableの違い

Closeable は AutoCloseable を継承した入出力向けのインターフェースで、違いは「close が宣言する例外」と「何度呼んでもよいかの約束」です。

比べる点 AutoCloseable Closeable
パッケージ java.lang java.io
登場した版 Java 7 Java 5(Java 7 で AutoCloseable を継承)
close() の宣言 throws Exception throws IOException
2回呼んだとき 同じ結果になる実装が強く推奨(必須ではない) 2回目以降は何も起きないことが必須
主な実装 Connection・Statement・ResultSet・Stream・ExecutorService InputStream・Reader・Writer・Scanner
try-with-resources 使える 使える
import java.io.Closeable;
import java.io.IOException;

public class CloseableDiff {
    // Closeable:close() が投げられる検査例外は IOException
    static class FileLike implements Closeable {
        @Override public void close() throws IOException { System.out.println("close FileLike"); }
    }
    // AutoCloseable:throws を書かずにオーバーライドすると、呼び出し側の catch が不要
    static class Timer implements AutoCloseable {
        private final long start = System.nanoTime();
        @Override public void close() {
            System.out.println("close Timer " + (System.nanoTime() - start > 0 ? "(計測終了)" : ""));
        }
    }

    public static void main(String[] args) throws IOException {
        try (FileLike f = new FileLike(); Timer t = new Timer()) {
            System.out.println("本体");
        }
        // Closeable は AutoCloseable を継承している
        System.out.println(AutoCloseable.class.isAssignableFrom(Closeable.class));
    }
}

実行結果(java CloseableDiff.java、OpenJDK 21.0.10):

本体
close Timer (計測終了)
close FileLike
true

自作クラスで close() をオーバーライドするときは、throws を必要な範囲まで狭めるのがポイントです。throws Exception のまま実装すると、使う側は Exception を catch するか throws に書く必要があります。

public class NeedCatch {
    static class Res implements AutoCloseable {
        @Override public void close() throws Exception { }
    }
    public static void main(String[] args) {
        try (Res r = new Res()) {
            System.out.println("use");
        }
    }
}

コンパイル結果(javac NeedCatch.java、OpenJDK 21.0.10):

NeedCatch.java:6: error: unreported exception Exception; must be caught or declared to be thrown
        try (Res r = new Res()) {
                 ^
  exception thrown from implicit call to close() on resource variable 'r'
1 error

メッセージの implicit call to close() は「自動で呼ばれる close が投げる例外」という意味です。close() の throws を外す(または IOException などに狭める)と、このエラーは出なくなります。

自作するとき 選ぶインターフェース close() の宣言
ファイル・ソケットなど入出力を包む Closeable throws IOException
例外を投げない後始末(計測終了、一時的な設定を戻す) AutoCloseable throws なし
独自の検査例外を投げたい AutoCloseable throws 独自例外

Java 25 の API ドキュメント(AutoCloseable)でも、close() は throws Exception の宣言で、実装では具体的な例外に狭めること、何度呼んでも同じ結果になるようにすること、InterruptedException を投げないことが推奨されています。構文と仕様は Java 21 と同じで、この記事のコードはそのまま使えます(Java 25 では実行しておらず、公式ドキュメントでの確認です)。

つまずきやすい点と対処の一覧

症状・メッセージ 理由 対処
unreported exception ... implicit call to close() close() が検査例外を宣言している catch を付けるか、メソッドに throws を書く。自作クラスなら宣言を狭める
neither final nor effectively final try (r) の変数に再代入している 再代入をやめるか、丸かっこの中で宣言する
catch の中でリソースを使えない catch の時点で close 済み・スコープ外 必要な値は本体の中で取り出しておく
close の例外がログに出ない getMessage() だけを出力している 例外オブジェクトごとログに渡す。getSuppressed() を確認
内側のリソースが閉じられない 入れ子で1行に生成している 変数を分けて宣言する
メソッドから返した Stream が閉じている try-with-resources の中で return した 呼び出し側で try-with-resources を書く。または結果をリストにして返す
Stream closed 閉じたリソースを try の外で使った リソースの利用を try ブロックの中に収める

よくある質問

Q. try-with-resourcesにcatchやfinallyは必要ですか? A. どちらも省略できます。close() や本体が検査例外を投げる場合は、catch を付けるか、メソッドに throws を書きます。finally は「close のあとに行いたい後始末」があるときだけ書けば十分です。

Q. closeは必ず呼ばれますか? A. 丸かっこの中で宣言し、null 以外で初期化できたリソースは、本体が正常終了しても、例外や return で抜けても呼ばれます。途中のリソースの生成で例外が出た場合は、それまでに開いたものだけが逆順に閉じられます(実行結果で確認)。

Q. 抑制例外はいつ確認すればよいですか? A. 本体と close の両方で例外が出たときに発生します。スタックトレースを例外オブジェクトごと出力していれば Suppressed: として表示されるため、ふだんは特別なコードは要りません。メッセージだけを組み立てて出力している箇所では、getSuppressed() も合わせて出すと情報が残ります。

Q. フィールドに持っているリソースにも使えますか? A. try-with-resources は「1つのブロックの中で開いて閉じる」リソース向けです。フィールドに持って長く使うリソースは、持ち主のクラス自身が AutoCloseable を実装し、その close() の中でフィールドのリソースを閉じる形にすると、持ち主を try-with-resources で扱えます。

まとめ

  • try-with-resources は try (宣言) { ... } で close を自動で呼ぶ構文。対象は AutoCloseable の実装クラス
  • 閉じる順序は宣言の逆順。実行順は「本体 → close → catch → finally」
  • 本体と close の両方で例外が出たら、本体の例外が届き、close の例外は getSuppressed() に残る
  • finally で close すると、close の例外が本体の例外を上書きする。置き換えると両方が残る
  • Java 9 以降は try (r) で既存の変数を置ける(final または実質的に final)
  • ラップするリソースは 変数を分けて宣言すると、生成の途中で例外が出ても内側が閉じられる
  • 自作するなら入出力系は Closeable、それ以外は AutoCloseable にして close() の throws を狭める

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Java try-with-resourcesの使い方|finallyとの違い」

株式会社RJCでは、try-with-resourcesのようなJavaの基本構文を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?