1. 複数の catch: 異なる例外の処理
実際のプログラムでは、同じコードブロックがさまざまな種類の例外を送出することがあります。例えば、ファイルの読み取りでは FileNotFoundException や IOException が発生するかもしれず、データのパースでは NumberFormatException も起こり得ます。各状況を正しく処理するために、Java では複数の catch ブロックを連続して使用できます。
構文
try {
// 危険な処理
} catch (FileNotFoundException e) {
System.out.println("ファイルが見つかりません: " + e.getMessage());
} catch (IOException e) {
System.out.println("入出力エラー: " + e.getMessage());
} catch (NumberFormatException e) {
System.out.println("数値の形式が不正です: " + e.getMessage());
}
重要なポイント:
catch ブロックは、より具体的なものからより一般的なものへ並べる必要があります! 最初に catch (Exception e) と書いてしまうと、他のブロックは到達不能となり、コンパイラがエラーを出します。
なぜ重要か?
- 特化した処理: エラーの種類ごとに異なる対応ができます(例: 別のファイルを選ばせる、再入力を促す)。
- 可読性: どのエラーを想定し、どう処理するのかが見て取れるため、コードが理解しやすくなります。
2. 複数型をまとめた catch(マルチキャッチ)
例外の種類が異なっても、同じ処理でよい場合があります。例えば、ファイルの読み取りエラーか数値変換エラーかに関わらず、エラーメッセージを表示する、あるいはログに記録するだけでよいといったケースです。
Java 7 からは、複数の例外型を 1 つのブロックにまとめられる multi-catch(マルチキャッチ)構文が導入されました:
try {
// 危険な処理
} catch (IOException | NumberFormatException e) {
System.out.println("ファイルまたは数値の処理中のエラー: " + e.getMessage());
}
仕組み:
- 縦棒(|)で例外型を並べて指定します。
- 変数 e は最も共通な基底クラスの型(通常は Exception)として扱われます。
- ブロック内で変数 e に別の値を代入することはできません。自動的に final とみなされます。
例:
try {
BufferedReader reader = new BufferedReader(new FileReader("numbers.txt"));
String line = reader.readLine();
int number = Integer.parseInt(line);
System.out.println("数値: " + number);
reader.close();
} catch (IOException | NumberFormatException e) {
System.out.println("エラー: " + e.getMessage());
}
3. 例外処理のベストプラクティス
例外を握りつぶさない
悪い例:
try {
// 危険なことをする
} catch (Exception e) {
// 何もしない!
}
なぜ悪いのか?
空の catch ブロックはエラーを「握りつぶし」てしまい、原因情報を失います。結果としてプログラムはデバッグ不能になります。
より良い例: 少なくともメッセージを出力するか、例外をログに記録しましょう。
catch (Exception e) {
e.printStackTrace();
}
その場で処理できない例外は、呼び出し元へスローしましょう:
catch (IOException e) {
throw e; // または throw new RuntimeException(e);
}
できるだけ具体的な例外を投げる
独自のメソッドや API を作る場合は、単なる Exception や RuntimeException ではなく、できる限り具体的な例外を送出しましょう。そうすることで、コードとインターフェースは他の開発者にとって理解しやすくなります。
例:
public void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException("残高不足です");
}
// ...
}
例外を制御フローの手段として使わない
例外は例外的な状況、つまり通常の実行では起きるべきでないエラーを処理するための仕組みです。通常のロジック(例えば、ループの終了や条件の判定)に使ってはいけません。
悪い例:
try {
while (true) {
String line = reader.readLine();
if (line == null) break;
// 行の処理
}
} catch (Exception e) {
// 例外でループを抜ける — よくない!
}
良い例:
String line;
while ((line = reader.readLine()) != null) {
// 行の処理
}
4. 実用的な例
例: エラーの種類ごとに別々に処理する
try {
BufferedReader reader = new BufferedReader(new FileReader("data.txt"));
String line = reader.readLine();
int number = Integer.parseInt(line);
System.out.println("数値: " + number);
reader.close();
} catch (FileNotFoundException e) {
System.out.println("ファイルが見つかりません!");
} catch (IOException e) {
System.out.println("ファイル読み取りエラー!");
} catch (NumberFormatException e) {
System.out.println("ファイル内は数値ではありません!");
}
例: 同一処理のためのマルチキャッチ
try {
// 危険なことをする
} catch (IOException | NumberFormatException e) {
System.out.println("エラー: " + e.getMessage());
}
5. 重要な注意点とよくある誤り
エラー No.1: catch ブロックの順序が不適切。 まずはより具体的な例外、続いて一般的な例外を書く必要があります。そうしないと、広い方のブロックに実行が到達することはありません。
エラー No.2: マルチキャッチで親子関係にある型を並べる。 例えば、catch (IOException | Exception e) はコンパイルできません。IOException は Exception のサブクラスだからです。
エラー No.3: マルチキャッチ内で変数 e を変更しようとする。 この場合、e は final と見なされるため、別の値を代入できません。
エラー No.4: 空の catch ブロック。 エラーを無視すると「サイレント」なバグにつながります。最低限、メッセージを出力するか例外をログに記録しましょう。
エラー No.5: 何でもかんでも Exception を捕まえる。 catch (Exception e) を使うと実際の問題が隠れ、デバッグが難しくなります。想定している例外だけを捕捉する方が良いです。
GO TO FULL VERSION