📖 読了目安 約9分
この記事の目次

例外処理の基本

プログラム実行中に発生するエラー(例外)を適切に処理する方法を学びます。

例外とは

例外(Exception)は、プログラム実行中に発生する異常事態です。

通常の処理 → 問題発生! → 例外が発生 → 処理が中断

例外を適切に処理しないと、プログラムが突然停止してしまいます。

よくある例外の例

例外の種類 発生する状況
NullPointerException nullのオブジェクトを使おうとした
ArrayIndexOutOfBoundsException 配列の範囲外にアクセスした
FileNotFoundException ファイルが見つからない
NumberFormatException 文字列を数値に変換できない
IOException 入出力でエラーが発生した

try-catch-finally の基本

基本構文

try {
    // エラーが発生する可能性のある処理
    File file = new File("data.txt");
    // ファイル読み込み処理
} catch (FileNotFoundException e) {
    // ファイルが見つからない場合の処理
    System.out.println("ファイルが見つかりません");
} catch (IOException e) {
    // その他のIO例外の処理
    System.out.println("読み込みエラー: " + e.getMessage());
} finally {
    // 必ず実行される処理(リソースの解放など)
}

各ブロックの役割

flowchart TB
    A[try ブロック] --> B{例外発生?}
    B -->|Yes| C[catch ブロック]
    B -->|No| D[finally ブロック]
    C --> D
    D --> E[次の処理へ]
ブロック 役割 必須?
try 例外が発生する可能性のある処理を書く 必須
catch 例外が発生したときの処理を書く 必須(1つ以上)
finally 例外の有無に関わらず必ず実行する処理 任意

例外処理のポイント

1. 例外を握りつぶさない

// NG:例外を無視している
try {
    processData();
} catch (Exception e) {
    // 何もしない ← これは危険!
}

// OK:少なくともログを出力
try {
    processData();
} catch (Exception e) {
    logger.error("データ処理でエラー発生", e);
    throw e;  // 必要に応じて再スロー
}

なぜダメなのか:エラーが発生しても気づけず、問題の発見が遅れます。

2. 適切な粒度でcatchする

// NG:大雑把すぎる
try {
    processData();
} catch (Exception e) {  // 全ての例外をまとめてキャッチ
    System.out.println("エラーです");
}

// OK:種類ごとに処理を分ける
try {
    processData();
} catch (FileNotFoundException e) {
    System.out.println("ファイルが見つかりません: " + e.getMessage());
} catch (IOException e) {
    System.out.println("ファイル読み込みエラー: " + e.getMessage());
}

なぜ大切か:エラーの種類によって対処方法が異なるため。

3. ログを適切に出力する

try {
    processData();
} catch (Exception e) {
    // ログには詳細情報を出力
    logger.error("データ処理でエラー: userId=" + userId, e);

    // ユーザーには分かりやすいメッセージを表示
    showErrorMessage("処理中にエラーが発生しました。管理者に連絡してください。");
}

出力すべき情報

  • エラーの種類とメッセージ
  • どの処理で発生したか
  • 関連するデータ(ユーザーID、入力値など)
  • スタックトレース

4. リソースは確実に解放する

// 従来の書き方
FileReader reader = null;
try {
    reader = new FileReader("data.txt");
    // ファイル読み込み処理
} catch (IOException e) {
    logger.error("ファイル読み込みエラー", e);
} finally {
    // エラーが起きても必ずファイルを閉じる
    if (reader != null) {
        try {
            reader.close();
        } catch (IOException e) {
            logger.error("ファイルクローズエラー", e);
        }
    }
}

// try-with-resources(Java 7以降、推奨)
try (FileReader reader = new FileReader("data.txt")) {
    // ファイル読み込み処理
} catch (IOException e) {
    logger.error("ファイル読み込みエラー", e);
}
// 自動的にクローズされる

例外の種類

チェック例外と非チェック例外

種類 特徴
チェック例外 コンパイラがチェック。try-catchまたはthrowsが必須 IOException, SQLException
非チェック例外 コンパイラはチェックしない。実行時に発生 NullPointerException, IllegalArgumentException
// チェック例外:必ずtry-catchまたはthrowsが必要
public void readFile() throws IOException {
    FileReader reader = new FileReader("data.txt");
}

// 非チェック例外:try-catchは任意(でもチェックした方がよい)
public void process(String value) {
    if (value == null) {
        throw new IllegalArgumentException("valueはnullにできません");
    }
}

例外処理のパターン

パターン1:リトライ処理

一時的なエラーの場合、リトライすることがあります。

int maxRetry = 3;
for (int i = 0; i < maxRetry; i++) {
    try {
        connectToServer();
        break;  // 成功したらループを抜ける
    } catch (ConnectionException e) {
        logger.warn("接続失敗。リトライします: " + (i + 1) + "/" + maxRetry);
        if (i == maxRetry - 1) {
            throw e;  // 最後の試行でも失敗したら例外をスロー
        }
        Thread.sleep(1000);  // 1秒待ってリトライ
    }
}

パターン2:デフォルト値を返す

エラー時にデフォルト値で処理を続行します。

public int parseAge(String ageStr) {
    try {
        return Integer.parseInt(ageStr);
    } catch (NumberFormatException e) {
        logger.warn("年齢の変換に失敗。デフォルト値を使用: " + ageStr);
        return 0;  // デフォルト値
    }
}

パターン3:例外を変換してスロー

下位層の例外を上位層に適した例外に変換します。

public User findUser(int userId) throws UserNotFoundException {
    try {
        return database.query("SELECT * FROM users WHERE id = ?", userId);
    } catch (SQLException e) {
        // データベース例外をビジネス例外に変換
        throw new UserNotFoundException("ユーザーが見つかりません: " + userId, e);
    }
}

よくある間違い

1. 何でもExceptionでキャッチ

// NG
try {
    doSomething();
} catch (Exception e) {  // 全部まとめてキャッチ
    // どんなエラーか分からない
}

2. 例外メッセージだけログ出力

// NG:スタックトレースが出力されない
logger.error("エラー: " + e.getMessage());

// OK:スタックトレースも出力
logger.error("エラー: " + e.getMessage(), e);

3. catchブロックで別の例外が発生

// NG:catchブロック内でさらに例外が発生する可能性
try {
    readFile();
} catch (IOException e) {
    writeErrorLog();  // これも例外を投げる可能性がある!
}

まとめ

例外処理の基本原則

原則 説明
例外を握りつぶさない 最低限ログを出力する
適切な粒度でcatchする 例外の種類ごとに処理を分ける
ログを出力する 何が起きたか後から分かるように
リソースを解放する finallyまたはtry-with-resourcesを使う

良い例外処理の特徴

  • エラーの原因が分かる
  • 適切なエラーメッセージが表示される
  • ログから問題を追跡できる
  • プログラムが適切に回復または終了する

関連ドキュメント