ほげにっき

hogedigoの日記

Java7 AutoCloseableを使ってスマートにリソース解放する

Javaでは昔からリソースの解放がやたら面倒くさかった。
デストラクタはないしfinalizeはいつ呼ばれるかわからないので、try-finallyで明示的に解放メソッドを呼ぶ必要がある。finally節ではさらにnullチェックとか入ってひたすらややこしいコードになる。

InputStream in = null;
OutputStream out = null;
try {
    in = new FileInputStream("...");
    out = new FileOutputStream("...");
    
    // 例えばストリーム間のコピーとか
    StreamUtils.copy(in, out);
} catch {
    (snip)
} finally {
    if (out != null) {
        try {
            out.close();
        } catch (IOException e) {
            // closeもまた検査例外だからcatchしなきゃ
            logger.warn("close failed.", e);
        }
    }
    if (in != null) {
        try {
            in.close();
        } catch (IOException e) {
            // closeもまた検査例外だからcatchしなきゃ
            logger.warn("close failed.", e);
        }
    }            
}


ちなみに今まではClosingという自前ユーティリティを使って、コードを簡略化していた。
Closingはざっくりとこんなカンジ↓

public static class Closing {

    private List<Closeable> closeables = new ArrayList<Closeable>();
	
    public <T extends Closeable> T later(T closeable) {
        // リソースをため込んでおく。
        closeables.add(closeable);
        return closeable;
    }
	
    public void atLast() {
        // 逆順に解放する
        CloseUtils.close(closeables);
        closeables.clear();
    }
}

laterが引数をそのまま戻しているのがミソ。
こんなカンジに書ける↓

Closing closing = new Closing();
try {
    
    // 例えばストリーム間のコピーとか
    StreamUtils.copy(
        closing.later(new FileInputStream("...")),
        closing.later(new FileOutputStream("...")));
} catch {
    (snip)
} finally {
    closing.atLast();     
}

手前味噌だがなかなか便利だった(^^;


前置きが長くなったが本題に戻る。
Java7でAutoCloseableというインタフェースとtry-with-resourcesとう構文が追加された。
これでリソース解放はかなり簡略に書ける様になった。

try (InputStream in = new FileInputStream("...");
        OutputStream out = new FileOutputStream("...")) {
    
    // 例えばストリーム間のコピーとか
    StreamUtils.copy(in, out);    
}

これだけ。なんて便利。
AutoCloseableインタフェースを実装しているオブジェクトはtry節のスコープが外れる際に自動でcloseメソッドが呼び出される。従来からあるCloseableインタフェースはAutoCloseableを継承しているので、Closebaleなクラスは全てtry-with-resources構文で使用することが出来る。


便利なのは記述の簡略化だけではない。

An exception can be thrown from the block of code associated with the try-with-resources statement. In the example writeToFileZipFileContents, an exception can be thrown from the try block, and up to two exceptions can be thrown from the try-with-resources statement when it tries to close the ZipFile and BufferedWriter objects. If an exception is thrown from the try block and one or more exceptions are thrown from the try-with-resources statement, then those exceptions thrown from the try-with-resources statement are suppressed, and the exception thrown by the block is the one that is thrown by the writeToFileZipFileContents method. You can retrieve these suppressed exceptions by calling the Throwable.getSuppressed method from the exception thrown by the try block.

http://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html#suppressed-exceptions

従来finally節のcloseで発生した例外は(検査例外なのでまたcatchしなければならないが)、try節で発生したもっと重要な例外をつぶしてしまうので再throwが難しかった。なので大概はclose時の例外は仕様上容認して、エラーログを出力するのみに留めたりした。
Java7のtry-with-resources構文では、AutoCloseableのclose時に発生した例外もきちんとスローされる。try節で例外が発生していた場合はもちろんそれが優先してスローされるが、その場合でも、close時例外が全てsuppressed exceptionsとして含まれておりThrowable#getSuppressedメソッドで取得することが出来る。その辺りについては別のエントリで追記する。→書いた


結局何が書きたかったというと、今までホクホク顔で使っていた自前ユーティリティが無用になってしまい少し寂しいということ(^_^;
まあJavaが標準で便利になるのは喜ばしいことだ。

一応自前ユーティリティもAutoCloseableにすることもできるけど・・・

try (Closing closing = new Closing()) {
    
    // 例えばストリーム間のコピーとか
    StreamUtils.copy(
        closing.later(new FileInputStream("...")),
        closing.later(new FileOutputStream("...")));
} catch {
    (snip)
}

あまりメリットないかな・・・