Java | Java 標準ライブラリ:例外階層構造

Java Java
スポンサーリンク

例外階層構造を一言でいうと

Java の「例外階層構造」は、

「エラーの種類を、親子関係(クラスの継承)で整理した“族譜”」

です。

この“族譜”を理解しておくと、

  • どの例外をキャッチすべきか
  • どこで throws すべきか
  • 自作例外をどこにぶら下げるべきか

が、スッと腑に落ちるようになります。

大きな骨格から順にかみ砕いていきます。


一番上の親:Throwable とその二大派閥(Error / Exception)

Throwable が“例外ツリー”の根っこ

Java で「投げられるもの(throw されるもの)」は、すべて

java.lang.Throwable
Java

を継承しています。

Throwable は、例外・エラーの共通の親クラスです。

Throwable
 ├─ Error
 └─ Exception

という形で、大きく二つに分かれます。

Error:基本的に“手に負えない”システムレベルのエラー

java.lang.Error は、JVM やシステムの深刻な問題を表します。

代表例としては、

OutOfMemoryError
StackOverflowError
NoClassDefFoundError
Java

などがあります。

これらは、

  • 普通のアプリケーションコードでは基本「catch しない」
  • 起きたら「プロセスごと落ちる」レベルの重大事

として扱われます。

初心者向けには、

Error は、アプリ側がどうにかしようとしないもの」

と覚えておいてください。

Exception:アプリケーションが扱う“普通の例外”

一方 java.lang.Exception は、

アプリケーションが「ちゃんと対処すべき」例外

です。

ファイルが無い
ネットワークがつながらない
入力データが不正
など、日常的に起こりうる「失敗」を表します。

ここからさらに、
「チェックされる例外(checked)」と「チェックされない例外(unchecked)」に分かれていきます。


Exception の二系統:チェック例外と実行時例外

RuntimeException を境界に考える

Exception の下には、大きく次の 2 パターンがあります。

Exception
 ├─ RuntimeException(実行時例外/unchecked)
 └─ それ以外の Exception(チェック例外/checked)

ここが Java の例外設計で一番重要なポイントです。

Java コンパイラは、

RuntimeException とそのサブクラス
→ throws 宣言や try-catch を強制しない(unchecked)

それ以外の Exception
→ throws 宣言か try-catch を強制する(checked)

というルールで扱います。

チェック例外(checked exception)とは

RuntimeException のサブクラス「以外」の Exception は、
チェック例外(checked exception)と呼ばれます。

代表例は、

IOException
SQLException
ParseException
Java

などです。

これらを投げるメソッドは、必ず

void readFile() throws IOException
Java

のように throws を宣言する必要があります。

そして、そのメソッドを呼び出す側は、

  • try-catch で捕まえる
  • さらに自分も throws で投げ直す

のどちらかを「コンパイラに強制」されます。

例:

import java.io.IOException;
import java.nio.file.*;

public class CheckedExample {
    public static void main(String[] args) {
        try {
            String content = Files.readString(Path.of("data.txt"));
            System.out.println(content);
        } catch (IOException e) {          // IOException は checked
            e.printStackTrace();
        }
    }
}
Java

ここで重要なのは、

「チェック例外は、呼び出し側に『ちゃんと考えろ』とコンパイラが迫ってくる」

という点です。

実行時例外(RuntimeException, unchecked exception)とは

RuntimeException とそのサブクラスは、実行時例外(unchecked exception)です。

代表例は、

NullPointerException
IllegalArgumentException
IndexOutOfBoundsException
ArithmeticException
ClassCastException
Java

など。

これらは、

  • メソッド側が throws を付けなくてもいい
  • 呼び出し側も try-catch を強制されない

という扱いです。

例:

public class RuntimeExample {
    public static void main(String[] args) {
        String s = null;
        System.out.println(s.length()); // NullPointerException(宣言も catch も強制されない)
    }
}
Java

コンパイルは通ります。
実行して初めて、NullPointerException が飛びます。

実行時例外は、

  • プログラマーのバグ(null チェック忘れ、範囲外アクセスなど)
  • 正しく使えば避けられるエラー

を表すことが多いです。

初心者の頭の中では、

チェック例外(Exception だが RuntimeException ではない)
→ 外部要因(ファイル・ネットワーク・DB など)、ちゃんと設計段階で考える失敗

実行時例外(RuntimeException)
→ コードのバグ、呼び出し側の使い方のミス

というざっくりした分類を持っておくと整理しやすいです。


例外階層をざっくり図でイメージする

主要なクラスを簡略化した“マップ”

概念図(ざっくり)をテキストで書くと、だいたいこんな感じです。

Throwable
 ├─ Error
 │   ├─ OutOfMemoryError
 │   ├─ StackOverflowError
 │   └─ ...
 └─ Exception
     ├─ RuntimeException
     │   ├─ NullPointerException
     │   ├─ IllegalArgumentException
     │   ├─ IndexOutOfBoundsException
     │   └─ ...
     ├─ IOException
     │   ├─ FileNotFoundException
     │   └─ EOFException
     ├─ SQLException
     ├─ ParseException
     └─ ...

すべてを暗記する必要はありません。

  • 大分類:Throwable → Error / Exception
  • Exception の中での大分類:RuntimeException 系(unchecked)/それ以外(checked)

この二つをしっかり押さえておけば、あとは必要になった子クラスを調べれば十分です。


catch の書き方と、階層構造がどう効いてくるか

親クラスを catch すると、子クラスも全部捕まる

例外は継承関係にあるので、

try {
    // 何か
} catch (IOException e) {
    // FileNotFoundException など IOException の子クラスもここで捕まる
}
Java

のように、親クラスで catch すると、
そのサブクラスもすべてまとめて捕まります。

逆に、

try {
    // 何か
} catch (FileNotFoundException e) {
    ...
}
Java

とすると、「ファイルが見つからない場合だけ」に絞ってキャッチできます。

階層構造があるおかげで、

  • ざっくり「I/O でのエラー」をまとめて扱いたいときは IOException
  • もっと細かく「ファイルが無いときだけ特別扱いしたい」ときは FileNotFoundException

のように、粒度を選べるわけです。

catch の順番に注意(より具体的なものを先に)

継承関係のある例外を複数 catch するときは、「子 → 親」の順に書く必要があります。

try {
    ...
} catch (FileNotFoundException e) {
    // まずは、より具体的な例外
} catch (IOException e) {
    // より広い例外(親)
}
Java

逆にするとコンパイルエラーです。

try {
    ...
} catch (IOException e) {
    ...
} catch (FileNotFoundException e) { // コンパイルエラー:到達不能
    ...
}
Java

親の IOException が先に来ているので、
FileNotFoundException はどう頑張ってもそこまで到達しない(全部親で捕まってしまう)からです。

これは、

階層構造を理解していないと「なぜコンパイルエラーなのか」が分かりにくいポイント

なので、「catch は基本的に“子から親へ”」と覚えておいてください。


自作例外をどこにぶら下げるか(checked vs unchecked)

RuntimeException を継承するか、Exception を継承するか

独自の例外クラスを作るとき、よく迷うのが継承元です。

例:

public class MyException extends Exception {
    public MyException(String message) {
        super(message);
    }
}
Java

とするか、

public class MyRuntimeException extends RuntimeException {
    public MyRuntimeException(String message) {
        super(message);
    }
}
Java

とするか。

判断基準はシンプルで、

  • 呼び出し側に「この失敗と真剣に向き合ってほしい」「例外処理を設計に組み込みたい」
    Exception を継承して checked にする
  • 「主にプログラミングミスや、事前条件違反を表したい」
    RuntimeException を継承して unchecked にする

という感じです。

例えば、「必ず存在するはずの設定ファイルがなぜか存在しない」など、
アプリの仕様としては「ありえない」前提にしている状況なら、RuntimeException にすることもあります。

逆に、「ユーザーの入力や外部サービスによって普通に起こりうるエラー」なら、checked exception で明示的に扱うほうが親切です。

初心者のうちは、

  • 自作するなら、とりあえず RuntimeException 継承(実行時例外)にしておく
  • 本当に設計上「呼び出し側に強制したい」例外だけ、Exception を継承する

くらいで十分です。


まとめ:例外階層構造を自分の中でこう位置づける

Java の例外階層を初心者向けにまとめると、こうなります。

Throwable
…… 例外ツリーの根っこ。「投げられるもの」の親クラス

Error
…… JVM やシステムレベルの致命的な問題。普通は catch しない

Exception
…… アプリが扱う例外の親

RuntimeException(そのサブクラス)
…… 実行時例外(unchecked)。コンパイラは throws / catch を強制しない。バグや事前条件違反の表現に使われがち

それ以外の Exception
…… チェック例外(checked)。コンパイラが throws / catch を強制。外部要因など「ちゃんと考えるべき失敗」

この「縦の関係」が分かると、

  • catch の書き方(子から親)
  • throws 宣言が必要かどうか
  • 自作例外をどこにぶら下げるか

の判断が、一気にクリアになります。

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