唐突ですが、Javaクイズです。

次のコードを実行した結果、表示されるのはe1, e2, どちらの例外でしょう?

public class ExceptionTrace {
    public static void main(String[] args) {
        try {
            method();
        } catch (Exception e) {
            System.err.println(e.getMessage());
        }
        return;
    }
    private static void method() throws Exception {
        try {
            ouch("1");
        } catch (Exception e1) {
            throw e1;
        } finally {
            try {
                ouch("2");
            } catch (Exception e2) {
                throw e2;
            }
        }
    }
    private static void ouch(String i) throws Exception {
        throw new Exception("exception " + i);        
    }
}

タイポ修正しました。(2007/03/10)*1
ちょっと考えてみてくださいね。


実行してみれば一発ですが結果は以下のとおりです。

exception 2


※白文字にしてあります。

Java言語仕様 第3版 (The Java Series)
これはJava言語仕様第三版 p355の定義に書いてあります。

catchブロックが理由Rで途中終了した場合、finallyブロックが実行される。その後、以下の選択が行われる。
finallyブロックが正常終了した場合、tryブロックは理由Rで途中終了する。
finallyブロックが理由Sで途中終了した場合、tryは理由Sで途中終了する(理由Rは破棄される)。

引用文の2行目、3行目の「try」, 「tryブロック」は「try文」のことだと思います。

で、何が言いたいのかというと、この場合e1の例外情報は消失しています。
例外情報が消失したら原因解析が難しくなります。


catchした例外はその場でとりあえずログに出しておいたほうがよさそうです。

} catch (Exception e1) {
    log.error("exception.", e1);
    throw e1;
} finally {

なんでこのクイズを書いたかというと。

アフィリエイトでアサマシしてみたかったからです。


うそです。はてなタダで使ってるのでアフィれません。



自分もよくやるのですが、ストリームを使うときにIOExceptionを上位例外(業務例外とかシステム例外とか)に変換せずにそのままスローしている場合、メソッドシグネチャにthrows IOExceptionが付いたままになります。


そーするとですね。ストリームのクローズをfinally節でやっていてもcloseがIOExceptionをスローするのを忘れていて、それをcatchし忘れたりします。
finally節で直にthrow new Exception("");と書くとコンパイラによって「到達不能(unreachable)」エラーが出るのですが、メソッド呼び出しを介すと解析してくれません。*2
これIOExceptionは検査例外だからまだいいけど非検査例外は気づけないなぁ。。。

ということでfinally節でcloseに失敗したら、catchした例外は消失しますので気をつけよう。というお話でした。
# レアケースかなー

*1:コメントでタイプミス見つけてもらったので修正しました。ありがとうございます。

*2:解析したら死ぬほど時間が掛かりそうですが…っていうかリフレクションとか使われたら解析不能だしDIコンテナとか使ってても解析不能だし。