JDK7 を使う準備が出来たので、体当たりします。
Java7 での個人的に嬉しいのが try-with-resources Statement です。AutoCloseable (を実装したクラス)の変数をこの構文で初期化すると、勝手に close してくれるってものです。
ではさらっと使ってみます。
public class TryWithResources { public static void main(String[] args) { try (AutoCloseable imp = new AutoCloseableImpl()) { // New! System.out.println("hoge"); } catch(Exception e) { System.out.println("catch:" + e); } finally { System.out.println("finally"); } } } class AutoCloseableImpl implements AutoCloseable { @Override public void close() throws Exception { System.out.println("close"); } }
素直に実行するとこうなりました。
hoge close finally
コンストラクタが例外を投げちゃうと close は呼ばれません。
catch:java.lang.RuntimeException finally
close が例外を投げる場合は catch されます。
hoge close catch:java.lang.Exception finally
なお、try の後ろでフィールドの宣言と初期化をすれば良いので、これでもコンパイル出来ます。null の closeメソッドを実行しようとしちゃうので NullPointerException になりますけれども。 *1
try (AutoCloseable c = null) { }
リソースは複数使えるので、2つ使った上で、try 内で例外を投げてみます。
public class TryWithResources { public static void main(String[] args) { try (AutoCloseable imp1 = new AutoCloseableImpl(); AutoCloseable imp2 = new AutoCloseableImpl()) { System.out.println("hoge"); throw new IllegalStateException(); } catch (Exception e) { System.out.println("catch:" + e); } finally { System.out.println("finally"); } } } class AutoCloseableImpl implements AutoCloseable { public AutoCloseableImpl() { System.out.println(this.hashCode()); } @Override public void close() throws Exception { System.out.println("close:" + this.hashCode()); } }
初期化された逆順で close され、その後に例外処理が行われました。
536841258 1112222159 hoge close:1112222159 close:536841258 catch:java.lang.IllegalStateException finally
2つめの初期化で例外を投げた場合、1つめのみ close されました。
1112222159 1420162825 close:1112222159 catch:java.lang.RuntimeException: 1420162825 finally
2つめの close で例外を投げても、1つめはちゃんと close されました。
536841258 1112222159 hoge close:536841258 catch:java.lang.RuntimeException: 1112222159 finally
2つとも例外を投げた場合、最初に投げられた方が catch されました。
1112222159 1420162825 hoge catch:java.lang.RuntimeException: 1420162825 finally
「もう片方の例外が消えてしまう!ありえない!これだから最近の若いもんは…」なんて方のために、Throwable.getSuppressed() があります。以下のコードは、主処理で例外が投げられ、両方の close でも投げられる場合です。
public class TryWithResources { public static void main(String[] args) { try (AutoCloseable imp1 = new AutoCloseableImpl(); AutoCloseable imp2 = new AutoCloseableImpl(imp1)) { System.out.println("hoge"); throw new RuntimeException("hoge"); } catch (Exception e) { System.out.println("catch:" + e); for (Throwable t : e.getSuppressed()) { // New! System.out.println("suppressed:" + t); } } finally { System.out.println("finally"); } } }
最初に投げられた例外が catch され、他は suppressed に入りました。
83812901 2041638322 hoge catch:java.lang.RuntimeException: hoge suppressed:java.lang.RuntimeException: 2041638322 suppressed:java.lang.RuntimeException: 83812901 finally
以上を踏まえて同様の挙動を Java6 で実装してみようとしたら、こんなことになってしまいました。なお Throwable.getSuppressed() も Java7 からなので、そこは諦めました。
public class J6TryWithResources { public static void main(String[] args) { try { Closeable a = null; boolean closeA = false; Closeable b = null; boolean closeB = false; try { a = new CloseableImpl(); closeA = true; b = new CloseableImpl(); closeB = true; System.out.println("hoge"); } finally { Exception e = null; try { if (closeB) { b.close(); } } catch (Exception ex) { e = ex; } try { if (closeA) { a.close(); } } catch (Exception ex) { if (e == null) { e = ex; } } if (e != null) { throw e; } } } catch (Exception e) { System.out.println("catch:" + e); } finally { System.out.println("finally"); } } }
もう訳がわかりませんね。おとなしく新しい構文を使いましょう。
*1:2011/2/27当時の1.7.0-eaでの挙動。1.7.0_11で実行したらcloseは実行されませんでした。