例外処理の基本
プログラム実行中に発生するエラー(例外)を適切に処理する方法を学びます。
例外とは
例外(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を使う |
良い例外処理の特徴
- エラーの原因が分かる
- 適切なエラーメッセージが表示される
- ログから問題を追跡できる
- プログラムが適切に回復または終了する
関連ドキュメント