try-with-resources
close() の行に、たどり着けないことがある
ファイルを開いて 1 行読むだけのコードです。最後にきちんと close() も書いてあります。
Java
BufferedReader reader = new BufferedReader(new FileReader(path));
String line = reader.readLine();
System.out.println(line.trim());
reader.close();ところが空のファイルを渡すと readLine() が null を返し、line.trim() で NullPointerException が飛びます。処理はその場で打ち切られるので、最後の reader.close() は実行されません。ファイルは開いたままです。
ここが厄介なのは、すぐには何も壊れないことです。ガベージコレクタが片付けてくれるのは Java のヒープの中だけで、OS が持っているファイルハンドルやソケット、データベース接続は close() を呼ぶまで戻ってきません。同じ処理を数千回繰り返したあと、Too many open files でサーバーが止まって初めて気づきます。
finally に移せば例外が出ても呼ばれますが、本筋ではないコードのほうが長くなります。
Java
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(path));
System.out.println(reader.readLine().trim());
} finally {
if (reader != null) {
reader.close();
}
}try のかっこに書くと、勝手に閉じてくれる
Java 7 以降は、try の直後のかっこの中でリソースを宣言できます。
Java
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
System.out.println(reader.readLine().trim());
}これだけです。かっこの中で宣言した変数は try ブロックの中だけで有効で、ブロックを抜けるときに close() が自動で呼ばれます。正常に抜けたときも、途中で例外が飛んだときも同じです。閉じ忘れが構文レベルで起こらなくなったわけです。
かっこの中には、セミコロンで区切って複数のリソースを並べられます。閉じられる順番は宣言と逆順です。あとから開いたものが先に閉じるので、接続の上に文を載せるような依存関係があっても、自然な順序で片付きます。
かっこに書けるのは AutoCloseable だけ
try のかっこに置けるのは、java.lang.AutoCloseable を実装した型に限られます。中身は close() メソッド 1 つだけの、とても小さなインタフェースです。
BufferedReader InputStream Scanner Connection といった標準ライブラリのリソース系クラスは、すべて実装済みです。自分で書いたクラスも implements AutoCloseable と宣言して close() を用意すれば、同じように置けます。実装を忘れると required type that implements AutoCloseable というコンパイルエラーで止まります。
よくある間違い
いちばん多い勘違いは、「これを使えば例外処理そのものが要らなくなる」というものです。自動になるのは close() の呼び出しだけで、ブロックの中で起きた例外はそのまま外へ飛んでいきます。受け止めたいなら catch を書き足してください。かっこと catch は併用できます。
もう 1 つ、close() の中に重い処理や独自の例外を仕込むのも避けましょう。後始末は静かに終わるのが理想です。
要件
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()を呼ばないこと
入出力例
dummyClose() → "opened-used-closed"