Java | Java 標準ライブラリ:非チェック例外

Java Java
スポンサーリンク

非チェック例外を一言でいうと

「非チェック例外(unchecked exception)」は、

“コンパイラが try-catch や throws 宣言を強制してこない例外”
かつ
“たいていはプログラマーのバグや、事前条件違反を表す例外”

です。

Java では、

Exception のうち RuntimeException とそのサブクラス
これが「非チェック例外(unchecked)」

という扱いになります。

チェック例外と違って、
「起こりうることをコード上でちゃんと扱いなさい」とは言われません。
だからこそ、「そもそも起こさないように書く」ことが大事になります。


例外階層の中での立ち位置(RuntimeException 系)

Throwable → Exception → RuntimeException の流れ

大まかな階層はこうでした。

Throwable
Error
Exception

Exception の中で、さらに二種類に分かれます。

RuntimeException とそのサブクラス
チェックされない例外(非チェック例外、unchecked)

RuntimeException 以外の Exception
コンパイラに対応を強制される例外(チェック例外、checked)

つまり、「非チェック例外=RuntimeException 系」と覚えてしまって構いません。

なぜ「非チェック」なのか

コンパイラは、RuntimeException 系に対しては、

throws を書けとも
try-catch で捕まえろとも

何も言ってきません。

例えば、NullPointerException が起きうるコードを書いても、
コンパイルは普通に通ります。

public class UncheckedExample {
    public static void main(String[] args) {
        String s = null;
        System.out.println(s.length()); // 実行時に NullPointerException
    }
}
Java

このコードはコンパイル OK です。
実行すると NullPointerException で落ちますが、
それは「実行して初めて分かる」性質のものです。

チェック例外は「ちゃんと扱ってね」とコンパイル時に迫ってくるのに対して、
非チェック例外は「お前の書き方が悪いと落ちるよ」とだけ言われているイメージです。


代表的な非チェック例外と「何が悪いのか」

NullPointerException

最も有名で、最もよく見る例外です。

「null 参照に対してメソッド呼び出しやフィールドアクセスをしたとき」に起こります。

public class NullPointerSample {
    public static void main(String[] args) {
        String s = null;
        int len = s.length(); // ここで NullPointerException
    }
}
Java

本質的には、

「そこに“何か”ある前提で書いているのに、実際は null(何もない)だった」

という、設計か実装のミスです。

回避すべきは、「catch してごまかすこと」ではなく、
null が入り込まないように設計や前処理を見直すことです。

IllegalArgumentException

「メソッドの引数が仕様に反しているとき」に投げる例外です。

自分で投げる場面も非常によくあります。

public class IllegalArgumentSample {
    public static void main(String[] args) {
        setAge(-1); // ここで IllegalArgumentException を投げるべき
    }

    static void setAge(int age) {
        if (age < 0) {
            throw new IllegalArgumentException("年齢は 0 以上である必要があります: " + age);
        }
        System.out.println("年齢 = " + age);
    }
}
Java

「このメソッドは、こういう前提で呼んでね」に違反したとき、
RuntimeException(特に IllegalArgumentException)で知らせるのが典型です。

IndexOutOfBoundsException / ArrayIndexOutOfBoundsException

配列やリストで存在しないインデックスを指定したときに起こります。

public class IndexOutOfBoundsSample {
    public static void main(String[] args) {
        int[] arr = {10, 20, 30};
        System.out.println(arr[3]); // 0〜2 までしかないので例外
    }
}
Java

これも「プログラムのバグ」であり、
try-catch して握りつぶすのではなく、
インデックスの計算ロジックを修正すべき例外です。

ArithmeticException

ゼロ除算などの算術エラーで投げられます。

public class ArithmeticSample {
    public static void main(String[] args) {
        int x = 10 / 0; // ArithmeticException
    }
}
Java

やはり、「事前に分かるべき状況」なので、
呼び出し前にチェックするか、ビジネスロジックを見直す必要があります。


チェック例外との違いを、コンパイル時の挙動で体感する

チェック例外はコンパイルエラーになる

例えば、Files.readString は IOException(チェック例外)を投げる可能性があります。

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

public class CheckedDemo {
    public static void main(String[] args) {
        String s = Files.readString(Path.of("data.txt")); // コンパイルエラー
        System.out.println(s);
    }
}
Java

このコードはコンパイルが通りません。

IOException(チェック例外)が発生しうるのに、
try-catch も throws も書いていないからです。

修正として、例えばこう書きます。

public static void main(String[] args) throws IOException {
    String s = Files.readString(Path.of("data.txt"));
    System.out.println(s);
}
Java

あるいは try-catch で囲みます。

非チェック例外はコンパイルエラーにならない

一方、RuntimeException は throws 宣言も try-catch も不要です。

public class UncheckedDemo {
    public static void main(String[] args) {
        String s = null;
        System.out.println(s.length()); // コンパイルOK、実行時に例外
    }
}
Java

コンパイラは「落ちる可能性」を教えてくれません。

ここが実務ではかなり効いてきます。

「チェック例外は、コードを書いている時点で“ここで失敗しうる”と意識せざるを得ない」
「非チェック例外は、テストや本番で落ちて初めて気付くこともある」

だからこそ、非チェック例外の多くは「そもそも起こさない設計」を大事にすべき領域です。


非チェック例外を try-catch すべきか?の考え方

原則:バグ由来の RuntimeException を握りつぶすのは悪手

例えば、次のように書くのは良くありません。

try {
    String s = null;
    System.out.println(s.length());
} catch (NullPointerException e) {
    // 何もしない
}
Java

これは、

「バグで落ちるはずのところを、見えなくしているだけ」

です。

エラーとして表面化した方が良い場所で、
「例外処理している気分」になってしまい、原因究明が遅れます。

RuntimeException 系は、「基本はバグ」なので、

テストで早く気付いて直す
ログを残して落ちるならまだマシ

という扱いの方が、長期的には健全です。

どうしても try-catch したいケース

とはいえ、非チェック例外でも try-catch が有効な場合はあります。

ログを取ってから再スローする
特定のバグを検知して、システムを安全に停止させる

など、「ただ握りつぶすのではない」使い方です。

例えば、

try {
    doBusinessLogic();
} catch (RuntimeException e) {
    log.error("致命的なエラー。システムを停止します", e);
    throw e;  // 再スローして、プロセス自体は終了させる
}
Java

のように、「捕まえて、記録して、また投げる」はよくあるパターンです。

重要なのは、

RuntimeException を catch したとき、「何をしたいのか」が明確であること
単に printStackTrace() して、処理を続けたりしないこと

です。


自作の非チェック例外(RuntimeException 継承)の使いどころ

自分用の RuntimeException を定義する

自分のドメイン(業務ロジック)で、
「これは致命的で、基本は呼び出し側がどうこうする話ではない」
と判断できるエラーには、自作の RuntimeException を使うことがあります。

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

    public BusinessRuntimeException(String message, Throwable cause) {
        super(message, cause);
    }
}
Java

これを使って、

public class UserService {
    public User findUserById(String id) {
        try {
            return repository.find(id); // ここでチェック例外が出る想定
        } catch (SQLException e) {
            throw new BusinessRuntimeException("ユーザー取得に失敗しました", e);
        }
    }
}
Java

というように、「この層ではチェック例外を扱わず、RuntimeException に包んで上位へ投げる」設計もよく使われます。

これは、

この層では DB のことを気にしたくない
エラーはアプリ全体のハンドラでまとめて拾う

といったアーキテクチャ上の理由から行うものです。

非チェックにするか、チェックのままにするか

判断の軸として、

呼び出し側に「この例外に向き合ってほしい」か
それとも「バグや致命的異常として落ちていい」か

を考えるのが良いです。

前者ならチェック例外(Exception 継承)
後者なら非チェック例外(RuntimeException 継承)

と割り振る、というイメージです。


まとめ:非チェック例外を自分の中でこう位置づける

非チェック例外(unchecked exception)を一言でまとめると、

「RuntimeException とそのサブクラスで、コンパイル時には強制されず、主にプログラムのバグや事前条件違反を表す例外」

です。

ポイントは、

  • コンパイラは throws / try-catch を強制しない
  • 落ちるかどうかは実行してみないと分からない
  • NullPointerException や IllegalArgumentException などが典型
  • 基本は「起こさないように書くべき」性質のもの
  • どうしても設計上必要なら、自作 RuntimeException にラップすることもある

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