3秒でわかる
実行中に起きたエラーを捕まえて、処理の続きを決める仕組み。try と catch と finally で異常時の振る舞いを組み立てます。
もう少し詳しく
どういうものか
例外処理は、プログラムの実行中に発生した異常を、その場で落とさずに受け止めて対処する仕組みです。Javaでは3つの構成要素があります。try が監視する範囲、catch が例外を捕まえて対処する場所、finally が例外の有無にかかわらず必ず実行される場所です。
例外が発生すると、その行以降の try 内の処理は飛ばされ、型が一致する catch へ制御が移ります。一致する catch が無ければ、呼び出し元へ例外がそのまま投げ上げられます。
なぜ必要か
正常系のコードの中に異常の判定を混ぜると、本筋が読めなくなります。ファイルを開いて読んで閉じるだけの処理でも、開けない、途中で切れる、閉じられない、という失敗があり得ます。戻り値でいちいち成否を返す方式だと、確認の if が処理の間に挟まり続けます。例外処理は、正常時の流れを一続きに書き、異常時の対処を下にまとめて書けるようにします。
finally があることで、途中で異常が起きても後片付けが確実に走ります。
具体例
public int readCount(String path) {
try {
String text = Files.readString(Path.of(path));
return <a href="/glossary/integer-parseint" class="text-primary font-medium underline underline-offset-2 hover:text-primary-dark">Integer.parseInt</a>(text.trim());
} catch (NoSuchFileException e) {
// ファイルが無いのは想定内なので既定値で続ける
return 0;
} catch (<a href="/glossary/ioexception" class="text-primary font-medium underline underline-offset-2 hover:text-primary-dark">IOException</a> e) {
// 読み取り失敗は呼び出し元へ知らせる
throw new UncheckedIOException(e);
} catch (NumberFormatException e) {
throw new IllegalStateException("数値として読めない内容です", e);
}
}リソースの解放は finally に書くより、try に続けてかっこで宣言する形が確実です。
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
} // close は自動で呼ばれるつまずきやすいところ
捕まえて何もしない catch が最悪の書き方です。
try {
risky();
} catch (Exception e) {
// 何も書かない
}これは異常を無かったことにするだけで、後からデータの不整合として現れます。原因を追う手がかりも消えます。対処できないなら捕まえず、上へ投げるのが正解です。
Exception でまとめて捕まえるのも避けます。想定していない NullPointerException まで飲み込んでしまい、バグが隠れます。捕まえる型は具体的に指定します。
もうひとつ、finally の中で return すると、try や catch の戻り値や投げた例外が上書きされて消えます。finally には後片付けだけを書きます。
覚え方
例外処理は保険ではなく、異常時の進路変更です。捕まえたら、代わりの値を返す、上へ知らせる、記録して止める、のどれかを必ず決めます。どれも決められないなら、そこは捕まえる場所ではありません。