例外処理

1. 例外の基本

例外はプログラム実行中に発生する予期しないエラーや異常な状態を表します。Javaでは例外はオブジェクトとして扱われ、例外クラスの階層構造があります。

例外の種類

  • 検査例外(Checked Exception): コンパイル時にチェックされる例外。処理が必須。
  • 非検査例外(Unchecked Exception): 実行時例外(RuntimeException)のサブクラス。処理は任意。
  • エラー(Error): 深刻な問題を表し、通常アプリケーションでは回復不可能。

// 例外の階層構造
Throwable
  ├── Error (回復不可能なエラー)
  │     ├── OutOfMemoryError
  │     ├── StackOverflowError
  │     └── ...
  └── Exception
        ├── IOException (検査例外)
        ├── SQLException (検査例外)
        ├── RuntimeException (非検査例外)
        │     ├── NullPointerException
        │     ├── ArrayIndexOutOfBoundsException
        │     ├── ArithmeticException
        │     └── ...
        └── ...
            

2. try-catch文

例外が発生する可能性のあるコードはtryブロックで囲み、発生した例外はcatchブロックで捕捉して処理します。


try {
    // 例外が発生する可能性のあるコード
    int result = 10 / 0;  // ArithmeticExceptionが発生
    System.out.println("この行は実行されません");
} catch (ArithmeticException e) {
    // 例外が発生した場合の処理
    System.out.println("ゼロ除算が発生しました: " + e.getMessage());
}
System.out.println("プログラムは続行します");
            

上記の例では、ゼロ除算によりArithmeticExceptionが発生しますが、catch文で捕捉されるため、プログラムは異常終了せずに続行します。

3. 複数のcatchブロック

複数の種類の例外を処理するために、複数のcatchブロックを使用できます。


try {
    int[] numbers = {1, 2, 3};
    System.out.println(numbers[5]);  // ArrayIndexOutOfBoundsException
    int result = 10 / 0;             // ArithmeticException
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("配列の範囲外にアクセスしました: " + e.getMessage());
} catch (ArithmeticException e) {
    System.out.println("算術エラーが発生しました: " + e.getMessage());
} catch (Exception e) {
    System.out.println("その他の例外が発生しました: " + e.getMessage());
}
            

複数のcatchブロックを使用する場合、より具体的な例外クラスから先に記述し、より一般的な例外クラスを後に記述します。

Java 7以降のマルチキャッチ


try {
    // 例外が発生する可能性のあるコード
} catch (IOException | SQLException e) {
    // 両方の例外を同じ方法で処理
    System.out.println("I/OまたはSQLの例外が発生しました: " + e.getMessage());
}
            

4. finallyブロック

finallyブロックは、例外が発生してもしなくても必ず実行されるコードブロックです。リソースの解放などに使用されます。


FileReader reader = null;
try {
    reader = new FileReader("file.txt");
    // ファイル操作
} catch (IOException e) {
    System.out.println("ファイル読み込みエラー: " + e.getMessage());
} finally {
    // リソースの解放(例外が発生しても必ず実行される)
    if (reader != null) {
        try {
            reader.close();
        } catch (IOException e) {
            System.out.println("ファイルクローズエラー");
        }
    }
}
            

finallyブロックは、tryブロックやcatchブロック内でreturn文が実行された場合でも実行されます。

5. 例外のスロー

throwキーワードを使用して、明示的に例外をスローすることができます。


public void checkAge(int age) {
    if (age < 0) {
        throw new IllegalArgumentException("年齢は0以上である必要があります");
    }
    if (age > 120) {
        throw new IllegalArgumentException("年齢が不正です");
    }
    System.out.println("年齢は有効です: " + age);
}
            

例外をスローすると、そのメソッドの実行は中断され、呼び出し元に例外が伝播します。

6. メソッドでの例外宣言

検査例外(checked exception)を処理せずに呼び出し元に伝播させる場合は、メソッド宣言にthrows句を使用します。


public void readFile(String fileName) throws IOException {
    FileReader reader = new FileReader(fileName);  // IOExceptionが発生する可能性
    // ファイル操作
    reader.close();
}

// 呼び出し元でも例外処理が必要
public void processFile() {
    try {
        readFile("data.txt");
    } catch (IOException e) {
        System.out.println("ファイル処理エラー: " + e.getMessage());
    }
}
            

非検査例外(unchecked exception)の場合は、throws句は任意です。

7. カスタム例外

アプリケーション固有の例外を作成するには、ExceptionクラスまたはRuntimeExceptionクラスを継承します。


// 検査例外の作成
public class InsufficientFundsException extends Exception {
    private double amount;
    
    public InsufficientFundsException(double amount) {
        super("残高不足: 不足額 " + amount + "円");
        this.amount = amount;
    }
    
    public double getAmount() {
        return amount;
    }
}

// 非検査例外の作成
public class InvalidProductException extends RuntimeException {
    public InvalidProductException(String message) {
        super(message);
    }
}

// カスタム例外の使用例
public void withdraw(double amount) throws InsufficientFundsException {
    if (balance < amount) {
        throw new InsufficientFundsException(amount - balance);
    }
    balance -= amount;
}
            

8. try-with-resources

Java 7以降では、AutoCloseableインターフェースを実装したリソースを自動的に閉じるためのtry-with-resources構文が導入されました。


// Java 7以降の方法
try (FileReader reader = new FileReader("file.txt");
     BufferedReader bufferedReader = new BufferedReader(reader)) {
    String line;
    while ((line = bufferedReader.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    System.out.println("ファイル読み込みエラー: " + e.getMessage());
}
// readerとbufferedReaderは自動的にクローズされる
            

try-with-resources構文を使用すると、finallyブロックでリソースを明示的に閉じる必要がなくなり、コードがシンプルになります。

9. 例外処理のベストプラクティス

例外処理の原則

  • 具体的な例外をキャッチする: Exceptionよりも具体的な例外クラスをキャッチしましょう。
  • 例外を無視しない: 空のcatchブロックは避けましょう。
  • 例外情報を保持する: 例外を再スローする場合は、元の例外情報を保持しましょう。
  • リソースは確実に解放する: try-with-resourcesまたはfinallyブロックを使用しましょう。
  • ログを適切に記録する: 例外発生時には適切なログを残しましょう。

例外の再スロー


try {
    // 処理
} catch (IOException e) {
    // ログ記録など
    throw new ApplicationException("データ処理中にエラーが発生しました", e);
}
            

例外の変換


try {
    // データベース操作
} catch (SQLException e) {
    // SQLExceptionをアプリケーション固有の例外に変換
    throw new DataAccessException("データベースアクセスエラー", e);
}
            

10. 練習問題

問題1: 基本的な例外処理

ユーザーから2つの整数を入力として受け取り、割り算を行うプログラムを作成してください。ゼロ除算や不正な入力(数値以外)に対して適切な例外処理を行ってください。

問題2: カスタム例外の作成

銀行口座を表すBankAccountクラスを作成し、残高が不足している場合にスローするカスタム例外InsufficientFundsExceptionを実装してください。

問題3: try-with-resourcesの使用

ファイルからテキストを読み込み、各行の単語数をカウントするプログラムを作成してください。try-with-resources構文を使用してリソースを適切に管理してください。