try-with-resources を一言でいうと
try-with-resources は、
「使い終わったら必ず close() しなきゃいけないものを、自動で確実に閉じてくれる try 文の書き方」
です。
ファイル、ソケット、DB 接続など、
- 開いたまま忘れると、メモリや OS のリソースを食いつぶす
- 例外が起きても、最後にちゃんと
close()しないといけない
といった「お片付け必須のオブジェクト」を
安全・簡単に扱うための仕組みです。
なぜ try-with-resources が必要なのか(従来の書き方の問題点)
昔の典型パターン:finally で close する
try-with-resources 登場前(Java 6 まで)は、
ファイルを読むコードはたいていこんな形でした。
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class OldStyle {
public static void main(String[] args) {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("data.txt"));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close(); // 忘れずに close する必要がある
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
Javaここで注意しなければいけないポイントが多すぎます。
readerを先にnullで宣言finallyでnullチェックclose()も例外を投げるので、さらに try-catch が必要
「ファイルを開いて、1 行ずつ読みたい」だけなのに、
close 関連のコードがやたらと多いですよね。
しかも、ちょっと気を抜くと
finallyを書き忘れるclose()の例外を握りつぶしてしまう
など、バグの温床になります。
「絶対に閉じ忘れてはいけない」という重圧
ファイル・ソケット・DB 接続などは、
- 閉じ忘れると OS リソースが枯渇する
- 長時間動くサーバーだと、少しの漏れが積み重なって死ぬ
という、かなりクリティカルな性質を持っています。
それを「人間が毎回がんばって finally に書く」というやり方は、
厳しすぎるし、バグを招きやすい。
そこで生まれたのが try-with-resources です。
try-with-resources の基本構文と動き
構文の形
Java 7 以降では、次のように書けます。
try (ResourceType res = new ResourceType(...)) {
// res を使った処理
} catch (Exception e) {
// 例外処理
}
Javatry の後ろの丸かっこの中で作られた「リソース」は、
そのブロックを抜けるときに、自動で close() されます。
ファイルを読む例を、try-with-resources で書き直してみます。
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class NewStyle {
public static void main(String[] args) {
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
} // ここを出るとき、自動的に reader.close() が呼ばれる
}
}
Javaさっきの長い finally が、丸ごと消えました。
これだけで、
- close の書き忘れがなくなる
nullチェックが不要close()用の try-catch も不要
という、かなり大きなメリットが得られます。
具体的にいつ close されるか
流れとしては、次のようなイメージです。
try ( ... )の中でリソースを生成{ ... }の中の処理を実行- 正常終了でも、途中で例外が投げられても、ブロックを抜ける直前に
close()
つまり、
whileが全部回りきったあとreadLine()でIOExceptionが出て catch に飛ぶとき
どちらの場合でも、必ず close() されるわけです。
どんなものが「try-with-resources の対象」になれるのか
AutoCloseable / Closeable を実装しているクラス
try-with-resources で使えるのは、java.lang.AutoCloseable インターフェースを実装しているクラスです。
ファイル関連でよく出てくる、
InputStream/OutputStreamReader/Writerjava.sql.Connection/PreparedStatement/ResultSet
などは、いずれも AutoCloseable(またはサブインターフェースの Closeable)を実装しています。
だからこそ、
try (FileInputStream in = new FileInputStream("data.bin")) {
...
}
Javaや
try (Connection con = dataSource.getConnection()) {
...
}
Javaが、そのまま書けます。
自作クラスでも try-with-resources 対象にできる
自分で作ったクラスに対しても、AutoCloseable を実装すれば対象にできます。
class MyResource implements AutoCloseable {
void doWork() {
System.out.println("作業中...");
}
@Override
public void close() {
System.out.println("close() が呼ばれました");
}
}
public class CustomResourceExample {
public static void main(String[] args) {
try (MyResource res = new MyResource()) {
res.doWork();
}
}
}
Java実行すると、最後に必ず close() が呼ばれます。
内部でファイルやソケットを持つラッパークラスを自作する場合などに、
「片付けの責任」を自分のクラスに閉じ込めることができます。
複数リソースをまとめて扱う(順番にも意味がある)
try ( …; …; … ) で複数同時に管理
try-with-resources は、1 つだけでなく複数のリソースを並べられます。
import java.io.*;
public class MultiResourceExample {
public static void main(String[] args) {
try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Javaここでは、
- ファイルを読む
reader - 別のファイルに書く
writer
の 2 つを、1 つの try-with-resources でまとめて管理しています。
close される順番
複数リソースがある場合、
逆順 に close() されます。
上の例でいうと、
writer.close()reader.close()
という順で呼ばれます。
これは、「後から開いたものを先に閉じる」
という自然なルールです。
多くのケースでは意識する必要はありませんが、
例えば「内側のリソースが外側のリソースに依存している」場合などに、
この順序が重要になることがあります。
例外との関係:try 部分と close 部分の両方で失敗したら?
ここは少し深めの話ですが、
try-with-resources をちゃんと理解するうえで重要です。
普通の try/finally だと起きがちな「例外の飲み込み」
従来の try-finally だと、こんなことが起きます。
- try の中で例外が投げられる
- finally で
close()したときに、さらに別の例外が投げられる
この場合、「どっちの例外を外に伝えるのか?」が問題になります。
下手をすると、
- 元々の本命の例外が消えてしまう
- close 中の例外がメインに見えてしまう
といった、デバッグしづらい状況になりがちです。
try-with-resources は「本命の例外」を優先する
try-with-resources では、
- try ブロックの中で発生した例外
- close() の中で発生した例外
のうち、「try 内の例外」が優先されます。
close 中の例外は、「サプレッシングされた例外」として
メインの例外にぶら下がる形になります(Throwable#getSuppressed() で取れる)。
初心者の段階では、
「try ブロック内で起きた上流側の例外が優先される」
くらいの理解で十分です。
重要なのは、
try-with-resources の方が例外情報を失いにくい、
つまり、デバッグがしやすいということです。
よくあるパターンを try-with-resources で書き直してみる
ファイル全行のコピー(Reader / Writer)
BufferedReader + BufferedWriter を使った定番パターンは、
すでに見た通りかなりスッキリ書けます。
try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
}
}
Javafinally での close 忘れを気にしなくてよくなるだけでなく、
コードの「本質」(ここではコピー処理)が読みやすくなります。
InputStream / OutputStream でも同じ
バイナリのコピーも同様です。
try (InputStream in = new FileInputStream("input.bin");
OutputStream out = new FileOutputStream("output.bin")) {
byte[] buf = new byte[8192];
int len;
while ((len = in.read(buf)) != -1) {
out.write(buf, 0, len);
}
}
Javatry-with-resources のおかげで、
- read/write のループに集中できる
- close の心配をほぼ忘れていい
という状態になります。
まとめ:try-with-resources を自分の中でどう位置づけるか
try-with-resources を初心者向けにまとめると、こうです。
- 「開いたら必ず閉じなきゃいけない資源」を、自動で確実に
close()してくれるtryの書き方 try ( ... ) { ... }の丸かっこ内で宣言したリソースは、ブロックを抜けるとき必ずclose()される- 対象は
AutoCloseableを実装したクラス(ファイル、ソケット、DB 接続などが代表例) - 従来の
try-finallyより、コードが短く、安全で、例外情報も失われにくい - 「I/O や DB まわりで、新しく書くコードは全部 try-with-resources にする」くらいの気持ちでちょうどいい
