日々常々

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

Java7 体当たり/catch-multiple

続けて例外処理にぶつかってみます。

Catching Multiple Exception Types and Rethrowing Exceptions with Improved Type Checking
複数の例外を一緒に処理出来るってやつです。複数の例外を投げるメソッドを使っても、大抵やりたい例外処理は同じだったりします。ログだしてオブジェクトの状態を回復するとか、投げ直すとか。身近な例だと、リフレクションAPI使う際とかが思い当たります。たまに Exception で処理しちゃってます。ゴメンナサイ。

これもざくっと書いてみます。

public class CatchingMultipleException {
    
    public static void main(String[] args) throws FirstException, SecondException {
        try {
            if (true) {
                throw new FirstException();
            } else {
                throw new SecondException();
            }
        } catch (FirstException | SecondException e) { // New!
            e.superMethod();
            throw e;
        }
    }
}

class SuperException extends Exception {
    
    public void superMethod() {
    }
}

class FirstException extends SuperException {

    public void firstMethod() {
    }
}

class SecondException extends SuperException {
    
    public void secondMethod() {
    }
}

バーティカルバーとかパイプとか縦線とか、口で言うときは話しかける相手によって変えなきゃいけなくて地味に困る | で catch したい例外を並べるだけです。「正規表現のorと同じです」とか適当な事を言っておきます。
上述の通り SuperException.superMethod が呼べるので、直近の親の型と解釈されるようです。当然 firstMethod や secondMethod は呼べません。複数の例外に同じ処理をしたいのが目的でしょうから、この辺は要らないでしょう。だからといって SuperException をキャッチしているのと同じではありません。仮に ThirdException なんて別のが出来たら挙動が変わりますし。

なお、以下のコードは Java7 ではコンパイル出来ますが Java6 ではできません。Java6 では throws SuperException と書かないといけません。

public class CatchingMultipleException {
    
    public static void main(String[] args) throws FirstException, SecondException { // New!
        try {
            if (true) {
                throw new FirstException();
            } else {
                throw new SecondException();
            }
        } catch (SuperException e) {
            e.superMethod();
            throw e;
        }
    }
}

これが Rethrowing Exceptions with Improved Type Checking とやらになります。キャッチした例外を投げ直す時に型が変わっていなければ、throws句の範囲を無駄に拡げる必要がなくなります。上記では SuperException 型なのに throws に書かれているのは FirstException, SecondException で良いのです。直近の共通親クラスがExceptionになる2種の例外を一度に処理した場合、従来ですと catch Exception と throws Exception とするしかありませんでした。そうしてしまうと意図しない例外も相手にしてしまう可能性がありました。
これが今回の変更で特定の複数の例外を一度に処理出来、throws に書くチェック例外も無闇に拡げる必要はなくなります。チェック例外については色々と議論がありますが、チェック例外ありきの進化としては正しいのじゃないかなーと思います。


ちなみに catch した Exception のフィールドに再代入なんてした場合は throws に catch した Exception の型を書かなきゃならなくなります。例えば以下のコードはコンパイルエラーになります。

public class CatchingMultipleException {
    
    public static void main(String[] args) throws FirstException, SecondException {
        try {
            if (true) {
                throw new FirstException();
            } else {
                throw new SecondException();
            }
        } catch (SuperException e) {
            e = new FirstException();
            throw e; // ここがコンパイルエラー
        }
    }
}

e に代入した段階で e の型が catch したものに限らなくなるから SuperException と解釈されるようになる感じ。入れてるのが FirstException だからいいだろーとかごねても駄目っぽいです。
catch (final SuperException e) とか書けば防げますけれど、そもそも e に再代入なんて考えた事も無いし、どちらもコンパイラがチェックしてくれるから問題ないと思います。


補足になりますが、Catching Multiple Exception ではそもそも再代入が出来ません。final的な何かになってるみたいです。

public class CatchingMultipleException {
    
    public static void main(String[] args) throws FirstException, SecondException {
        try {
            if (true) {
                throw new FirstException();
            } else {
                throw new SecondException();
            }
        } catch (FirstException | SecondException e) {
            e.superMethod();
            e = new FirstException(); // ここがコンパイルエラー
            throw e;
        }
    }
}