- JAVA
Java try-with-resourcesの使い方|finallyとの違い
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期連続の黒字決算。