例外階層構造を一言でいうと
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 宣言が必要かどうか
- 自作例外をどこにぶら下げるか
の判断が、一気にクリアになります。

