24
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Javaの例外処理を正しく理解する — 検査例外・非検査例外・throwsの使いどころ

24
Posted at

はじめに

Javaを学び始めると、例外処理は早い段階でつまずきやすいテーマのひとつです。

「とりあえず try-catch で囲めばいい」「throws Exception と書けばコンパイルが通る」という理解のまま進んでしまうと、バグの原因に気づきにくいコードになってしまうことがあります。

この記事では、例外処理を基礎から理解したい方に向けて、なぜそう書いてはいけないのかを、検査例外・非検査例外の違い、throws の正しい使いどころ、やってはいけないアンチパターンまで、順を追って解説します。


例外の全体像

まず、Javaの例外クラスの階層を把握しましょう。

Throwable
  ├── Error                        ← JVMレベルの深刻なエラー(基本的に対処不可)
  │     ├── OutOfMemoryError
  │     └── StackOverflowError
  │
  └── Exception                    ← プログラムで対処できる例外
        ├── IOException            ← 検査例外(Checked Exception)
        ├── SQLException           ← 検査例外
        ├── ClassNotFoundException ← 検査例外
        │
        └── RuntimeException       ← 非検査例外(Unchecked Exception)
              ├── NullPointerException
              ├── IllegalArgumentException
              ├── IndexOutOfBoundsException
              └── ClassCastException

Exception のサブクラスは大きく2種類に分かれます。

  • 検査例外(Checked Exception)RuntimeException 以外の Exception
  • 非検査例外(Unchecked Exception)RuntimeException とそのサブクラス

検査例外(Checked Exception)とは?

特徴

コンパイラが「必ず処理しなさい」と強制する例外です。

try-catch でキャッチするか、throws で呼び出し元に委ねないと、コンパイルエラーになります。

// IOException は検査例外なので、処理しないとコンパイルエラー
public void readFile(String path) {
    BufferedReader reader = new BufferedReader(new FileReader(path));
    // コンパイルエラー! IOException を処理していない
}

対処方法は2つあります。

// ① try-catch でキャッチする
public void readFile(String path) {
    try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
        String line;
        while ((line = reader.readLine()) != null) {
            System.out.println(line);
        }
    } catch (IOException e) {
        System.err.println("ファイルの読み込みに失敗しました: " + e.getMessage());
    }
}

// ② throws で呼び出し元に委ねる
public void readFile(String path) throws IOException {
    try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
        String line;
        while ((line = reader.readLine()) != null) {
            System.out.println(line);
        }
    }
}

代表的な検査例外

例外クラス 発生する場面
IOException ファイル・ネットワーク操作
FileNotFoundException ファイルが見つからない
SQLException DB操作
ClassNotFoundException クラスが見つからない
ParseException 日付などのパース失敗

なぜ検査例外が存在するのか?

「このメソッドを呼び出す人は、必ずエラーのことを考えてください」というJavaの設計思想です。ファイルやDBのような外部リソースへのアクセスは失敗しうるため、呼び出し元に対処を強制しています。


非検査例外(Unchecked Exception)とは?

特徴

処理しなくてもコンパイルが通る例外です。RuntimeException のサブクラスが該当します。

// NullPointerException は非検査例外なので、try-catch がなくてもコンパイルが通る
public void printLength(String str) {
    System.out.println(str.length()); // strがnullだとNullPointerExceptionが発生
}

代表的な非検査例外

例外クラス 発生する場面
NullPointerException nullオブジェクトのメソッド呼び出し
IllegalArgumentException 不正な引数が渡された
IllegalStateException オブジェクトの状態が不正
IndexOutOfBoundsException 配列・Listの範囲外アクセス
ClassCastException 不正なキャスト
ArithmeticException ゼロ除算など

なぜ非検査例外が存在するのか?

プログラムのバグが原因の例外は、毎回 try-catch で囲むのではなく、そもそもバグを直すべきという考え方があります。NullPointerException はnullチェックを適切にすれば防げます。全部 try-catch で囲むとコードが冗長になるため、非検査例外はコンパイラの強制なしに任せています。


try-catch-finally の使い方

try {
    // 例外が発生しうる処理
    int result = 10 / divisor;
} catch (ArithmeticException e) {
    // 例外が発生したときの処理
    System.err.println("計算エラー: " + e.getMessage());
} catch (Exception e) {
    // その他の例外(広いものは後に書く)
    System.err.println("予期しないエラー: " + e.getMessage());
} finally {
    // 例外の有無にかかわらず必ず実行される
    System.out.println("処理終了");
}

try-with-resources(Java 7以降)

Closeable を実装したリソース(ファイル、DB接続など)は、try-with-resources を使うと自動でクローズされます。

// try-with-resources を使う(推奨)
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
    String line = reader.readLine();
    System.out.println(line);
} catch (IOException e) {
    System.err.println(e.getMessage());
}
// reader.close() は自動で呼ばれる

// 昔の書き方(finally で手動クローズ)
BufferedReader reader = null;
try {
    reader = new BufferedReader(new FileReader("file.txt"));
    System.out.println(reader.readLine());
} catch (IOException e) {
    System.err.println(e.getMessage());
} finally {
    if (reader != null) {
        try { reader.close(); } catch (IOException e) { /* ignore */ }
    }
}

throws の使いどころ

throws はメソッドシグネチャに書き、「このメソッドはこの例外を投げることがある」と呼び出し元に伝えるためのものです。

// throws を使う例
public User findUser(int id) throws UserNotFoundException {
    User user = userRepository.findById(id);
    if (user == null) {
        throw new UserNotFoundException("ユーザーが見つかりません: id=" + id);
    }
    return user;
}

throws を使う判断基準

状況 対応
呼び出し元がエラーに対処できる throws で委ねる
このメソッド内で完結して対処できる try-catch でキャッチする
プログラムのバグが原因 throwstry-catch も不要(バグを直す)

自分で例外を投げる(throw)

独自のバリデーションなどで、自分で例外を投げることができます。

public void setAge(int age) {
    if (age < 0 || age > 150) {
        throw new IllegalArgumentException("年齢が不正です: " + age);
    }
    this.age = age;
}

独自例外クラスを作る

// 検査例外として作る場合
public class UserNotFoundException extends Exception {
    public UserNotFoundException(String message) {
        super(message);
    }
}

// 非検査例外として作る場合
public class InvalidOrderException extends RuntimeException {
    public InvalidOrderException(String message) {
        super(message);
    }
}

やってはいけないアンチパターン

① 例外を握り潰す

// ❌ 最悪のパターン:例外が発生しても何もしない
try {
    riskyOperation();
} catch (Exception e) {
    // 何も書かない(もしくはコメントアウト)
}

// ✅ 最低でもログを出す
try {
    riskyOperation();
} catch (Exception e) {
    logger.error("処理に失敗しました", e);
    throw e; // 必要であれば再スロー
}

throws Exception の乱用

// ❌ 何でもかんでも throws Exception
public void doSomething() throws Exception {
    // ...
}

// ✅ 具体的な例外を指定する
public void doSomething() throws IOException, SQLException {
    // ...
}

throws Exception は「どんな例外が起きるかわからない」というメッセージになり、呼び出し元が適切に対処できなくなります。

catch (Exception e) で何でもキャッチする

// ❌ すべての例外を一括でキャッチ(バグが隠れる)
try {
    String result = processData(null);
} catch (Exception e) {
    System.out.println("エラー");
}

// ✅ 想定する例外を具体的にキャッチ
try {
    String result = processData(null);
} catch (NullPointerException e) {
    System.out.println("nullが渡されました");
} catch (IllegalArgumentException e) {
    System.out.println("不正な引数です: " + e.getMessage());
}

まとめ

  • 検査例外:コンパイラが処理を強制。外部リソース操作など「失敗しうる」処理に使う
  • 非検査例外:処理は任意。プログラムのバグが原因のことが多い
  • throws:呼び出し元が対処すべき例外を委ねるときに使う
  • アンチパターン:例外の握り潰し・throws Exception の乱用・catch (Exception e) の乱用は避ける
24
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?