try-with-resources
try-with-resources とは
AutoCloseable なリソースを安全に扱う try-with-resources 構文を学び、リソースの自動クローズを体験しよう。
リソース解放という、永遠の悩み
Java でファイル、ネットワーク接続、データベース接続などを扱うとき、必ず付いて回るのが「使い終わったら閉じる」という後始末の作業です。FileInputStream を開いたなら close() を呼ばないと OS のファイルハンドルが残ったままになり、最悪の場合「Too many open files でアプリが停止」という障害につながります。Connection や Socket も同じで、閉じ忘れは時限爆弾です。
「メモリは
GC(ガベージコレクタ) が片付けてくれるから安心」と思いがちですが、GCが回収するのはあくまで Java のヒープの中だけです。OSの世界にある「ファイルハンドル」「ソケット」「DBコネクション」などは、Java 側で明示的にclose()を呼ばないと解放されません。これが Java プログラマがリソース管理を意識しないといけない最大の理由です。
そこで Java 7 で導入されたのが、本章の主役 try-with-resources です。これを使うと、try の宣言部にリソースを書くだけで、ブロックを抜けるときに自動で close() が呼ばれます。finally で泥臭く close() を書いていた時代から、ぐっと安全で読みやすいコードになります。
try-with-resources の基本構文
まず構文を見てみましょう。try の直後の () の中にリソースを宣言するのがポイントです。
Java
try (Resource r = new Resource()) {
r.use();
}try のあとに ( ... ) が付くこの記法が、try-with-resources の特徴です。() の中で宣言した変数 (r) は、try ブロックの中だけで有効なローカル変数になります。そして try ブロックを正常に抜けたときでも、例外で抜けたときでも、r.close() が 暗黙的に呼び出される ように Java コンパイラがコードを書き換えてくれます。
つまり開発者は、リソースを「new する」ところと「use() する」ところだけを書けば、後片付けは Java に任せられる、というわけです。finally ブロックを書く必要はありません。
旧来の try-finally との比較
try-with-resources がなかった時代、つまり Java 6 以前は、次のような重たいコードを書いていました。Before / After で並べてみます。
Java
// Before: Java 6 までの try-finally スタイル
Resource r = null;
try {
r = new Resource();
r.use();
} finally {
if (r != null) {
r.close();
}
}Java
// After: Java 7 以降の try-with-resources
try (Resource r = new Resource()) {
r.use();
}行数だけでなく、null チェックや finally ブロックといった「本筋ではないコード」が消え、書き手の意図が一目で伝わるようになりました。さらに重要なのは、旧スタイルだと r.close() 自体が例外を投げた場合に元の例外が握りつぶされてしまう、という落とし穴があったのですが、try-with-resources では close() の例外が Throwable.addSuppressed() で抑制例外として記録されるので、両方の情報が残ります。
旧スタイルで
close()をtryの中に書くと、use()が例外を投げたときにclose()が呼ばれない、という別のバグを生みます。finallyに書けば呼ばれますが、今度は例外が握りつぶされる、という地雷だらけの世界でした。try-with-resourcesは、この厄介な問題を文法レベルで解決してくれる仕組みです。
AutoCloseable インターフェース
try-with-resources の () の中に書けるのは、何でも良いわけではありません。java.lang.AutoCloseable インターフェースを実装したクラスだけが置けます。AutoCloseable の中身は、たった 1 つのメソッドです。
Java
public interface AutoCloseable {
void close() throws Exception;
}標準ライブラリの FileInputStream BufferedReader Connection Scanner などは、すべてこの AutoCloseable (もしくはサブインターフェースの java.io.Closeable) を実装済みです。だから上記のクラスを try-with-resources の () に書いておけば、ブロックを抜けるときに自動で close() が呼ばれる、という仕組みになっています。
自作のクラスでリソース管理をしたい場合は、implements AutoCloseable と書いて close() メソッドを実装するだけで OK です。今回の課題でもまさにこれを書きます。
上の図のとおり、正常終了でも例外発生時でも、必ず close() が呼ばれるところがポイントです。Java コンパイラが裏側で finally { r.close(); } 相当のコードを自動生成してくれている、と理解しておくとイメージしやすいでしょう。
複数リソースの宣言
try-with-resources の () の中には、セミコロン ; で区切って 複数のリソース を宣言できます。たとえば入力と出力を同時に開きたいときは次のとおりです。
Java
try (
BufferedReader in = new BufferedReader(new FileReader("in.txt"));
BufferedWriter out = new BufferedWriter(new FileWriter("out.txt"))
) {
String line;
while ((line = in.readLine()) != null) {
out.write(line);
out.newLine();
}
}このとき覚えておきたいのが、close() は 宣言と逆順 で呼ばれる、ということです。上の例では out を先に閉じてから in を閉じます。これは、ふつう「後から開いたものは先に閉じる」(LIFO) のがリソース管理の鉄則なので、Java もそれに従うように設計されています。たとえば Connection の上に PreparedStatement を載せた場合、当然先に PreparedStatement を閉じてから Connection を閉じたいわけです。
ちなみに Java 9 以降では、すでに宣言済みで
final(もしくは事実上final) な変数をtry-with-resourcesの()に直接書くこともできます。たとえばtry (resource) { ... }のように。とはいえまずは「try (Type name = expr)の形」を覚えておけば十分です。
今回の課題で作る Resource クラス
今回の課題では、ファイル IO のような重い仕組みを使わず、ログ文字列で「ライフサイクル」を観察できる軽量な Resource クラスを自分で書きます。Solution クラスの中に static inner class として定義するのがポイントです。
動きは次のとおりです。
- コンストラクタが呼ばれたら、
StringBuilderのログに"opened"を追記 use()を呼ばれたら、ログに"-used"を追記close()が自動で呼ばれたら、ログに"-closed"を追記- 最後にログ文字列
"opened-used-closed"をreturnする
この一連の流れを try-with-resources で書くと、たった 3 行で「開いて、使って、閉じる」が表現できます。close() を一切書いていないのに、-closed がちゃんとログに入るのを確かめましょう。
よくある間違い
try-with-resources で初学者がつまずく代表的なポイントを 3 つまとめます。書き始める前にざっと目を通しておくと、後でデバッグに時間を取られずに済みます。
implements AutoCloseableを忘れる —try (Resource r = ...)のResourceクラスがAutoCloseableを実装していないと、required type that implements AutoCloseableというコンパイルエラーになります。class Resource implements AutoCloseableと必ず書きましょうclose()の中で重い処理や例外を派手に投げる —close()は「後始末」のメソッドです。ここでネットワーク通信をしたり、独自の例外を派手に投げたりすると、デバッグが非常に難しくなります。close()は冪等 (idempotent) に、つまり何回呼んでも安全になるよう書くのが鉄則ですtryの()の中で複数リソースを書く順番を間違える —close()は宣言と逆順で呼ばれるので、依存関係 (たとえばConnection→Statement→ResultSet) がある場合は、依存される側を先に書き、依存する側を後に書きます。ConnectionStatementResultSetの順に宣言すれば、逆順でResultSet→Statement→Connectionの順に閉じられる、ということです
もう 1 つよくあるのが、「try-with-resources を使えば例外処理は不要」と勘違いするケースです。try-with-resources がやってくれるのは close() の自動呼び出しだけで、use() の中で起きた例外を握りつぶしてくれるわけではありません。例外を握りたいなら、別途 catch ブロックを書く必要があります。
Java
try (Resource r = new Resource()) {
r.use();
} catch (Exception e) {
// ここで例外をハンドリング
}このように try-with-resources と catch は併用できます。finally を書かなくても close() は呼ばれる、というのが新しい時代の Java の姿です。
やってみよう
それでは課題に挑戦してみましょう。次の手順で進めます。
Solutionクラスの中にstatic class Resource implements AutoCloseableを定義するResourceのフィールドにStringBuilder logを持たせ、コンストラクタでlog.append("opened")するvoid use()メソッドでlog.append("-used")するpublic void close()メソッドでlog.append("-closed")するSolution.dummyClose()の中でStringBuilderを作り、try (Resource r = new Resource(log)) { r.use(); }と書き、最後にreturn log.toString();する
テストは戻り値が "opened-used-closed" ちょうどになっているかを比較します。ハイフンの位置 (1 つ目だけ「opened」のように - が付かない) を間違えやすいので、最初の append だけ "opened"、後の 2 つは "-used" "-closed" と先頭にハイフンを付けるのがポイントです。
慣れてきたら、Resource を 2 つ宣言してみて、close() がどちらから呼ばれるかを試してみましょう。try (Resource a = new Resource(log); Resource b = new Resource(log)) のように書くと、a → b の順に open、b → a の順に close されるのが観察できます。実際の IO や DB 接続でも、この性質を活かして安全にリソースを解放するのが Java の流儀です。
よくある質問
Q. 中級の内容は実務でどれくらい使いますか?
A. Collection(List/Map/Set)、Stream、例外処理、ジェネリクスは毎日のように登場します。Date/Time API、Files、try-with-resources も実務で頻出するため、本コースの内容は実プロジェクトでそのまま役立ちます。
Q. Stream と for ループはどっちで書くべき?
A. 可読性で選んでください。filter + map + collect が綺麗にハマるなら Stream、副作用や複雑な分岐が多いなら for ループの方が読みやすいです。一律にどちらを使うべきという正解はなく、チームのコーディング規約に合わせるのが現実的です。
Q. 次のステップでは何を学ぶべきですか?
A. 中級の基礎が固まったら、Spring Boot で Web API を作る、JUnit でテストを書く、Maven/Gradle でビルドを管理する、といった実プロジェクトのスキルに進むと効果的です。OSS のコードを読む経験も大きく成長を促します。
次のレッスン
次は finally ブロック で、AutoCloseable なリソースを安全に扱う try-with-resources 構文を学び、リソースの自動クローズを体験しよう を学びます。
事前確認 — 進む前に次の 3 つができることを確認しましょう。
- try-with-resources の要点を自分の言葉で説明できる
- このレッスンの最小コード (または操作手順) を見ずに書ける
- 練習問題やクイズで間違えた箇所を読み直して理解した
理解度チェック (30 秒)
Q. try-with-resources とは何か、1 文で説明してください。
A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。
関連レッスン
要件
Solutionの中にstatic class Resource implements AutoCloseableを定義し、close()メソッドを実装することResourceのコンストラクタで"opened"、use()で"-used"、close()で"-closed"をStringBuilderにappendすることtry-with-resources構文 (try (Resource r = new Resource(log)) { ... }) を使い、finallyで明示的にclose()を呼ばないこと
入出力例
test-cases.txt
dummyClose() → "opened-used-closed"