この記事の要点
• 例外は実行時エラーをオブジェクトとして扱う仕組み
• try-catch-finally で異常系を安全にハンドリング
• Java 21 以降は try-with-resources でリソース解放が自動化
このシリーズについて
「Java入門」シリーズの第19回です。本記事では例外処理の基本構文と、checked / unchecked 例外の違いを学びます。
前提条件: #12 - クラスの基本、#13 - コンストラクタとメソッド を理解していること。
例外とは
プログラムが実行中に予期しない状況に遭遇したとき、Java は 例外(Exception) を投げて異常を通知します。
| 状況 | 投げられる例外 |
|---|---|
| 配列の範囲外にアクセス | ArrayIndexOutOfBoundsException |
| null のメソッド呼び出し | NullPointerException |
| 数値文字列でない値を parseInt | NumberFormatException |
| 0 で除算 | ArithmeticException |
| ファイルが存在しない | IOException |
例外が発生すると、何も対処しなければプログラムはクラッシュします。これを防ぐために try-catch を使います。
try-catch の基本構文
// Java 21 - 数値変換時の例外をキャッチする例
try {
String input = "abc";
int num = Integer.parseInt(input); // NumberFormatException が発生
System.out.println(num);
} catch (NumberFormatException e) {
System.out.println("数値に変換できません: " + e.getMessage());
}
// プログラムは続行される
| ブロック | 役割 |
|---|---|
try { ... } | 例外が発生する可能性のあるコードを囲む |
catch (例外型 変数) { ... } | 例外が投げられたときに実行される |
上記の例では parseInt("abc") が失敗しますが、catch で補足されるためプログラムは停止しません。
ポイント: e.getMessage() で例外の詳細メッセージを取得できます。デバッグ時は e.printStackTrace() でスタックトレースを出力すると原因特定が早くなります。
複数の catch ブロック
異なる例外型ごとに処理を分けることができます。
// Java 21 - 配列アクセスと数値変換の両方をハンドリング
String[] values = {"10", "20", "abc"};
try {
int index = 5; // 範囲外
int num = Integer.parseInt(values[index]);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("配列の範囲外です");
} catch (NumberFormatException e) {
System.out.println("数値変換に失敗しました");
}
catch は上から順に評価されます。親クラスの例外を先に書くと、子クラスの catch に到達しないため注意してください。
注意: catch (Exception e) のように親クラスを先に書くと、すべての例外をそこで捕捉してしまい、後続の catch が無視されます。具体的な例外を上に、汎用的な例外を下に配置しましょう。
finally ブロック
例外の有無に関わらず必ず実行したい処理は finally に書きます。
// Java 21 - ファイルを必ず閉じる(古い書き方)
FileReader reader = null;
try {
reader = new FileReader("data.txt");
// 読み込み処理
} catch (IOException e) {
System.err.println("読み込みエラー: " + e.getMessage());
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
// クローズ失敗も無視できないため二重 try が必要
}
}
}
finally は return 文の直前にも実行されます。ただし、ネストした try-catch が複雑になりがちです。
try-with-resources(推奨)
Java 7 以降は try-with-resources を使えば、リソースのクローズが自動化されます。
// Java 21 - try-with-resources(推奨)
try (FileReader reader = new FileReader("data.txt")) {
// 読み込み処理
} catch (IOException e) {
System.err.println("読み込みエラー: " + e.getMessage());
}
// try ブロックを抜けると reader.close() が自動で呼ばれる
AutoCloseable を実装したリソース(ファイル、DB接続、ネットワークソケット等)はすべて自動クローズされます。
ヒント: 複数のリソースを try (A a = ...; B b = ...) のようにセミコロン区切りで並べることもできます。閉じる順序は宣言の逆順です。
主要な例外クラス
Java 標準ライブラリが提供する代表的な例外を覚えておきましょう。
| 例外クラス | 発生する状況 |
|---|---|
NullPointerException | null のメソッド呼び出し、フィールドアクセス |
ArrayIndexOutOfBoundsException | 配列の範囲外アクセス |
NumberFormatException | parseInt 等で文字列を数値に変換失敗 |
ArithmeticException | 0 で除算(整数演算) |
IOException | ファイル読み書き、ネットワーク通信のエラー |
checked 例外 vs unchecked 例外
Java の例外は大きく2種類に分かれます。
| 種類 | 意味 | 代表例 | コンパイラのチェック |
|---|---|---|---|
| Checked Exception | 予期可能で回復可能なエラー | IOException, SQLException | try-catch または throws が必須 |
| Unchecked Exception | プログラムのバグや実行時エラー | NullPointerException, ArithmeticException | 任意(書かなくてもコンパイル通る) |
Checked Exception は Exception を継承し、RuntimeException を継承していない例外です。メソッド内で発生する可能性がある場合、呼び出し側で必ず try-catch するか、メソッド宣言に throws を付ける必要があります(次回 #20 で詳説)。
Unchecked Exception は RuntimeException を継承します。通常はバグなので、事前の if 文や論理修正で回避することが望ましく、catch は必須ではありません。
ポイント: checked 例外はコンパイル時に強制されるため、「ファイルが存在しないかもしれない」「ネットワークが切れているかもしれない」といった外部要因の異常に備えることができます。
小さな実行例:0除算の回避
// Java 21 - 0で除算しようとしたときの例外処理
import java.util.Scanner;
public class DivisionExample {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("分子を入力: ");
int numerator = sc.nextInt();
System.out.print("分母を入力: ");
int denominator = sc.nextInt();
try {
int result = numerator / denominator;
System.out.println("結果: " + result);
} catch (ArithmeticException e) {
System.err.println("エラー: 0で割ることはできません");
} finally {
System.out.println("処理が終了しました");
}
}
}
分母に 0 を入力すると ArithmeticException が発生しますが、プログラムはクラッシュせずに finally まで到達します。
プログラミングのベストプラクティス
| やるべきこと | 理由 |
|---|---|
| 具体的な例外型を catch する | catch (Exception e) だけだと原因が不明瞭 |
| ログを残す | e.printStackTrace() や logger を使う |
| finally は本当に必要なときだけ | try-with-resources で代替できるケースが多い |
| catch で握り潰さない | 空の catch ブロックは障害の温床 |
注意: 本番環境では printStackTrace() をコンソールに出力するのではなく、ロギングフレームワーク(SLF4J + Logback 等)を使ってファイルに記録しましょう。
次のステップ
Java入門 #20 - throws と例外の伝播 では、メソッド宣言に throws を付けて例外を呼び出し元に委譲する方法と、独自例外クラスの作り方を学びます。
参考リソース
- Oracle Java SE 21 - Exception — 公式 API ドキュメント
- The try-with-resources Statement — 公式チュートリアル
- Checked and Unchecked Exceptions