日々常々

ふつうのプログラマがあたりまえにしたいこと。

Java7 体当たり/try-with-resources Statement

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は実行されませんでした。