例外

Javaにはチェックされない例外と、チェックされる例外があって、これの使い分けは難しい。

Effective Javaでは、チェックされない例外をさらに実行時例外*1とエラー*2に分けていて、

新たなErrorサブクラスを実装しないことが最善です。
実装するすべてのチェックされない例外は、RuntimeExceptionをサブクラス化すべきです(直接的あるいは間接的に)。

Effective Java 項目40 回復可能な状態にはチェックされる例外を、プログラミングエラーには実行時例外を使用する

となっている。
また、Exception、RuntimeException、Errorを親に持たないような例外*3は使用すべきでない、ともある。


ということで、基本的にはRuntimeException系列の例外とException系列の例外のみを考えればいい。
だけど、この2つの使い分けがまた問題だ。
Effective Javaでは

プログラミングエラー*4
RuntimeException系列
回復可能な場合
Exception系列

という指針を提供している。
要は、プログラマのミスならRuntimeException系列を、予測可能な例外的状況にはException系列を使え、ということだ。
例えば、IOエラーは予測可能な例外的状況で、回復もまぁ可能だが、NullPointerExceptionは明らかにバグだろう。
ということで、基本的にはこの指針で問題ないんだけど、これはどうよ?って状況は少なからずある。


例えば、Integer.parseInt(String)は不正な文字列を与えるとNumberFormatExceptionを投げるが、NumberFormatExceptionはRuntimeException系列なので、キャッチすべきではないのだが、

String userInputStr1 = ...;
String userInputStr2 = ...;
if (userInputStr1.matches("-?[0-9]+") &&
        userInputStr2.matches("-?[0-9]+")) {
    int i = Integer.parseInt(userInputStr1);
    int j = Integer.parseInt(userInputStr2);
    ...
} else {
    ...
}

なんて書くより、

String userInputStr1 = ...;
String userInputStr2 = ...;
try {
    int i = Integer.parseInt(userInputStr1);
    int j = Integer.parseInt(userInputStr2);
    ...
} catch (NumberFormatException e) {
    ...
}

の方が分かりやすい。
逆に、リフレクションを使おうとすると、NoSuchMethodExceptionやらIllegalAccessExceptionやらInvocationTargetExceptionやら、これでもか、ってくらいのチェックされる例外を投げてくる。
InvocationTargetExceptionはチェックされる例外でもいいけど、NoSuchMethodExceptionとかIllegalAccessExceptionはチェックされない例外でいいんじゃ?と思ったりする。


Exception系列であって欲しいのにRuntimeException系列な時は、構わずcatchしてしまえば構文上は問題ない*5
RuntimeException系列であって欲しいのにException系列な時は、RuntimeException系列の例外でラップしてしまって再送すれば構文上も意味上も問題ない。


例外クラスを作るときには、それがプログラマエラーにより発生するのか違うのかや、回復可能かそうでないのかを完璧に判断することなんて不可能だから仕方がないんだけど、できる限り使う側を考慮して例外を作って欲しい。

*1:RuntimeExceptionとそのサブクラス

*2:Errorとそのサブクラス

*3:Throwableを直接継承するだとか

*4:バグとか、ミスとか

*5:できればやりたくないけど