例外処理
目次
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構文を使用してリソースを適切に管理してください。