Java | Java 詳細・モダン文法:言語仕様詳細 – マルチ catch

Java Java
スポンサーリンク

マルチ catch を一言でいうと

マルチ catch は、
複数の例外型を、同じ 1 つの catch ブロックでまとめて処理できる構文」です。

catch (IOException | SQLException e) のように、| で例外型を並べて書きます。
「この 2 つ(あるいはそれ以上)の例外は、同じ扱いでいいや」というときに、
重複した catch ブロックを書く必要がなくなります。


まずは「マルチ catch がない世界」を見てみる

従来の書き方:例外ごとに同じような catch が並ぶ

マルチ catch が導入される前(Java 7 より前)は、
複数の例外を同じように処理したくても、こう書くしかありませんでした。

try {
    doSomething(); // IOException も SQLException も投げるかもしれない
} catch (IOException e) {
    log.error("失敗しました", e);
    throw new RuntimeException(e);
} catch (SQLException e) {
    log.error("失敗しました", e);
    throw new RuntimeException(e);
}
Java

catch の中身が ほぼコピペ ですよね。
例外型だけ違って、やっていることは同じ。

こういう「中身が同じ catch が並ぶ」コードは、

  • 読みにくい
  • 修正漏れが起きやすい(片方だけ直してもう片方を忘れる)

という問題を抱えています。


マルチ catch の基本構文

| で例外型を並べる

同じ処理でよい例外をまとめて扱いたいとき、
マルチ catch を使うとこう書けます。

try {
    doSomething();
} catch (IOException | SQLException e) {
    log.error("失敗しました", e);
    throw new RuntimeException(e);
}
Java

catch (IOException | SQLException e) を日本語にすると、

IOExceptionSQLException のどちらかが飛んできたら、
それを e という変数で受けて、このブロックで処理する」

という意味です。

ポイントは、

  • | で例外型を並べる
  • 変数名(ここでは e)は 1 つだけ
  • ブロックの中では、その変数を「共通の親型」として扱う

ということです。


マルチ catch の「型」のイメージ

変数 e の型はどう見なされるか

catch (IOException | SQLException e) {
    ...
}
Java

このとき、e の静的型は「2 つの例外型の共通の親クラス」です。
IOExceptionSQLException の共通の親は Exception なので、
コンパイラ的には「eException として扱える」と考えられます。

そのため、ブロックの中では、
IOExceptionSQLException に特有のメソッドは呼べませんが、
Exception として共通の操作(getMessage() など)は問題なく使えます。

catch (IOException | SQLException e) {
    System.out.println(e.getMessage()); // OK(Exception のメソッド)
    // e.getSQLState(); // コンパイルエラー(SQLException にしかないメソッド)
}
Java

「マルチ catch の変数は、“列挙した例外型の共通の親”として扱われる」
という感覚を持っておくと、挙動が理解しやすくなります。


マルチ catch を使うときの制約と注意点

共通の親子関係にある型は一緒に書けない

例えば、次のようなマルチ catch はコンパイルエラーになります。

catch (IOException | Exception e) {
    ...
}
Java

IOExceptionException のサブクラスなので、
Exception を捕まえる」と書いた時点で IOException も含まれてしまいます。

つまり、

  • 親クラスとそのサブクラスを同じマルチ catch に並べるのは NG
  • 「どうせ全部 Exception で受けるなら、Exception だけ書けばいいよね?」

という話になります。

マルチ catch に並べるのは、
「兄弟関係にある例外型(共通の親を持つが、互いに親子ではない)」
にするのが基本です。

変数を再代入できない(実質 final)

マルチ catch の変数 e は、暗黙に「実質 final」です。

catch (IOException | SQLException e) {
    // e = new IOException(); // コンパイルエラー
}
Java

これは、マルチ catch に限らず、
「複数の例外型をまとめて扱うときに、変数を別の例外に差し替えるのは危険」
という考え方に基づいています。


マルチ catch が「効く」典型的な場面

例1:ログを出してラップして投げ直すだけのとき

さきほどの例のように、
「ログを出して、共通のラップ例外に包んで投げ直す」
というパターンは、マルチ catch のド定番です。

try {
    doSomething();
} catch (IOException | SQLException e) {
    log.error("処理に失敗しました", e);
    throw new RuntimeException("処理失敗", e);
}
Java

もしこれを個別の catch で書くと、
ほぼ同じコードが 2 回出てきてしまいます。

マルチ catch によって、

  • 「この 2 つの例外は、アプリケーションとしては同じ扱いでよい」
  • 「違いを意識する必要はない」

という設計意図を、コードで表現できます。

例2:入力チェック系の例外をまとめて扱う

例えば、ある処理の中で

  • NumberFormatException(文字列を数値に変換できない)
  • DateTimeParseException(文字列を日付に変換できない)

のどちらかが飛ぶ可能性があり、
どちらも「入力フォーマットエラー」として扱いたい場合。

try {
    parseInput(input);
} catch (NumberFormatException | DateTimeParseException e) {
    throw new IllegalArgumentException("入力フォーマットが不正です: " + input, e);
}
Java

「どの例外が飛んだかの細かい違いは気にせず、
ユーザーには“入力が不正”とだけ伝えたい」
というときに、マルチ catch はとても自然です。


マルチ catch を使うかどうかの判断基準

自分にこう問いかけてみる

マルチ catch を使うか迷ったら、
その catch に対して自分にこう聞いてみてください。

この例外たちを、“アプリケーションとしては同じ扱い”にしてよいか?

もし答えが「はい」なら、マルチ catch の候補です。

逆に、

  • 例外ごとにログメッセージを変えたい
  • 片方はリトライしたいが、もう片方は即座に失敗にしたい
  • 片方はユーザーに見せるメッセージを変えたい

といった「意味の違い」があるなら、
無理にマルチ catch にまとめず、
個別の catch に分けた方が読みやすくなります。

「とりあえず全部まとめる」は危険

マルチ catch が便利だからといって、

catch (IOException | SQLException | RuntimeException | Exception e) {
    ...
}
Java

のように、何でもかんでも突っ込むのは危険です。

それをやると、

  • どの例外がどこから来たのか分かりにくくなる
  • 本来分けて扱うべき例外まで、雑に一緒くたにされてしまう

という状態になります。

マルチ catch はあくまで、
意味的に同じ扱いでよい例外だけをまとめる
ための道具だと意識しておくと、設計が崩れにくくなります。


まとめ:マルチ catch を自分の言葉で説明するなら

あなたの言葉でマルチ catch を説明すると、こうなります。

「マルチ catch は、catch (A | B e) のように書いて、
複数の例外型を 1 つの catch ブロックでまとめて処理できる構文。
型チェックと変数は 1 回だけ書けばよく、
中身が同じ catch をコピペで並べる必要がなくなる。

変数 e は“列挙した例外型の共通の親型”として扱われ、
親子関係にある型同士を同じマルチ catch に並べることはできない。
『アプリケーションとして同じ扱いでよい例外だけをまとめる』
という意識で使うと、コードが短くなるだけでなく、
例外設計の意図もはっきり伝わる。」

タイトルとURLをコピーしました