はじめに
Java7から"try-with-resources"構文が追加されました。 ファイルやDBアクセスしたあとのリソース解放を自動で行ってくれる大変便利な機能で、解放し忘れをなくし、コードをすっきりさせることができます。 ただし、書き方によってリソースが解放されないパターンがあったので紹介します。
具体的には以下のような場合です。 リソース解放の対象クラスをネストさせてインスタンス生成した場合、コンストラクタで例外が発生するとリソース解放されません。
File file = new File("out.txt"); // PrintWriterがインスタンス生成に失敗すると、BufferedWriter・FileWriterが解放されない try(PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file)));) { // 処理 } // ・・・
検証
各Writerクラスにログを仕込み、どのような動作をするか検証してみました。
public class Test { public static void main(String[] args) { File file = new File("out.txt"); try (PrintWriter pw = new PrintWriterWrapper( new BufferedWriterWrapper( new FileWriterWrapper(file)));) { System.out.println("func"); } catch (Exception e) { System.out.println("catch:" + e); } finally { System.out.println("finally"); } } // 以下、ログを追加したラッパークラス public static class PrintWriterWrapper extends PrintWriter { public PrintWriterWrapper(Writer out) { super(out); System.out.println("new PrintWriter"); } @Override public void close() { System.out.println("close PrintWriter"); super.close(); } } public static class BufferedWriterWrapper extends BufferedWriter { public BufferedWriterWrapper(Writer out) { super(out); System.out.println("new BufferedWriter"); throw new RuntimeException(); } @Override public void close() throws IOException { System.out.println("close BufferedWriter"); super.close(); } } public static class FileWriterWrapper extends FileWriter { public FileWriterWrapper(File file) throws IOException { super(file); System.out.println("new FileWriter"); } @Override public void close() throws IOException { System.out.println("close FileWriter"); super.close(); } } }
処理が正常終了する場合、作成したインスタンスを逆順でリソース解放(closeメソッド実行)しています。
// ・・・ // ネストでインスタンス生成する try (PrintWriter pw = new PrintWriterWrapper( new BufferedWriterWrapper( new FileWriterWrapper(file)));) { System.out.println("func"); } // ・・・ // 実行結果(close()が実行されている) // new FileWriter // new BufferedWriter // new PrintWriter // func // close PrintWriter // close BufferedWriter // close FileWriter // finally
ただしコンストラクタで例外が発生した場合、内包するインスタンスに対するリソース解放がされません。
// ・・・ // ネストでインスタンス生成する try (PrintWriter pw = new PrintWriterWrapper( new BufferedWriterWrapper( new FileWriterWrapper(file)));) { System.out.println("func"); } // ・・・ // PrintWriterWrapperのコンストラクタで例外発生させる public PrintWriterWrapper(Writer out) { super(out); System.out.println("ERROR!! new PrintWriter"); thorow new RuntimeException(); } // ・・・ // 実行結果(close()が実行されない) // new FileWriter // new BufferedWriter // ERROR!! new PrintWriter // catch:java.lang.RuntimeException // finally
ポイントは以下の2点です。
検証1では、変数pwのcloseメソッドが実行され、内包するBufferedWriter、FileWriterを連鎖的にcloseしています。 検証2では変数pwのcloseメソッドが実行されず、内包するインスタンスも自動リソース解放の対象となっていないためそのまま残ってしまいます。
解決法:ネストせず個別に変数定義する
結論として、コンストラクタで例外が発生しないことが明白である場合以外は個別に変数定義するのが良さそうです。 以下の例ではPrintWriterのインスタンス生成に失敗した場合もBufferedWriter、FileWriterのcloseメソッドが実行されています。 (FileWriterのcloseメソッドが2回実行されているのは、BufferedWriterのcloseメソッドから連鎖的に実行されたのと変数fwとして宣言したため自動リソース解放の対象となっているためです。)
// ・・・ // 個別にフィールドを宣言し、それぞれインスタンス生成する try ( FileWriter fw = new FileWriterWrapper(file); BufferedWriter bw = new BufferedWriterWrapper(fw); PrintWriter pw = new PrintWriterWrapper(bw);) { System.out.println("func"); } // ・・・ // PrintWriterWrapperのコンストラクタで例外発生させる public PrintWriterWrapper(Writer out) { super(out); System.out.println("ERROR!! new PrintWriter"); thorow new RuntimeException(); } // ・・・ // 実行結果(close()が実行されている) // new FileWriter // new BufferedWriter // ERROR!! new PrintWriter // close BufferedWriter // close FileWriter // close FileWriter // catch:java.lang.RuntimeException // finally