安全なjava.io
よく見かけるパターン
public void foo() throws IOException { OutputStream out = new FileOutputStream("foo.txt"); try { //いろいろ } finally { out.close(); } }
これは良くない。
closeで例外が起こると、tryブロック内でthrowされたものを上書きしてしまう。
Errorでさえも闇に葬ってしまうのでかなりよろしくない。
IO処理に限らず、Errorの事を忘れて例外の発生するコードをfinally内に書いてしまいがちなので気をつけたい。
public void foo() throws IOException { OutputStream out = new FileOutputStream("foo.txt"); try { //いろいろ } finally { try { out.close(); } catch(Exception e) { //ログに出す } } }
これも良く見るパターン。
closeの例外はログに出して終わりと割り切ってしまえば、特に問題なく使える。
Errorが消えることも無い。
ログすらもいらんと、IOUtils#closeQuietlyを使うこともある。
手間が増えるので、これ以上の事をしているコードはあまり見ない。
面倒くさがらずこの条件で書いてみる。
- Errorは即throw
- tryブロック内で例外が出るとそれをthrow。closeも試みる。
- closeだけで例外が発生した場合はそれをthrow
public void foo() throws IOException { OutputStream out = new FileOutputStream("foo.txt"); try { // いろいろ } catch (IOException e) { try { out.close(); } catch (Exception e2) { } throw e; } catch (RuntimeException e) { try { out.close(); } catch (Exception e2) { } throw e; } out.close(); }
そう面倒でもなかった。
finallyを使うとフラグが必要になるので、使わないほうが楽。
毎回書くにはちょっと長いのでTemplateMethodにしてみる。
public abstract class Closing<T extends Closeable> { public Closing(T closeable) throws IOException { try { process(closeable); } catch (IOException e) { close(closeable, e); } catch (RuntimeException e) { close(closeable, e); } closeable.close(); } private <E extends Exception> void close(Closeable closeable, E exception) throws E, IOException { try { closeable.close(); } catch (IOException e) { throw exception; } catch (RuntimeException e) { throw exception; } } public abstract void process(T closeable) throws IOException; }
こんな使うときはこんな感じ。
new Closing<FileOutputStream>(new FileOutputStream("foo.txt")) { public void process(FileOutputStream out) throws IOException { // いろいろ } };
http://www.kawaz.jp/diary/?200502b&to=200502181#200502181
http://d.hatena.ne.jp/odz/20061016/1160974869
こちらで言及されている、コンストラクタで例外→close忘れもネストさせれば発生しない。
new Closing<FileOutputStream>(new FileOutputStream("foo.txt")) { public void process(FileOutputStream out) throws IOException { new Closing<Writer>(new OutputStreamWriter(out)) { public void process(Writer writer) throws IOException { // いろいろ } }; } };
テキストファイルコピー&文字数カウント&ダイジェスト取得メソッド
public static int foo(File src, final File dst, String encoding, final MessageDigest md) throws IOException { final int[] count = new int[1]; new Closing<FileInputStream>(new FileInputStream(src)) { public void process(FileInputStream in) throws IOException { new Closing<Reader>(new InputStreamReader(new DigestInputStream(in, md))) { public void process(final Reader reader) throws IOException { new Closing<FileOutputStream>(new FileOutputStream(dst)) { public void process(FileOutputStream out) throws IOException { new Closing<Writer>(new OutputStreamWriter(out)) { public void process(Writer writer) throws IOException { char[] cbuf = new char[2048]; int len = 0; while ((len = reader.read(cbuf)) != -1) { writer.write(cbuf, 0, len); count[0] += len; } } }; } }; } }; } }; return count[0]; }
読みにくい・・