クラッシュさせる代わりに例外をキャッチする
何かがうまくいかないとき、Javaは例外を投げ、処理されない例外はスタックトレースとともにプログラムを停止させる、ということはもうご存じでしょう。try-catchブロックは制御を握るための方法です。リスクのあるコードをtryで包めば、例外が投げられてもJavaはクラッシュせず、一致するcatchブロックへ飛びます。
10 / 0が例外を投げた瞬間、tryブロックの残りはスキップされ、制御はcatchへ移ります。catchが終わると、実行は通常どおり続きます。プログラムは死にません。
catchの変数が例外を保持する
catch (ArithmeticException e)のeは本物のオブジェクトです。何がうまくいかなかったかについての情報を持っており、もっとも役立つのはメッセージです。
e.getMessage()は短い説明を返します。デバッグ中は、e.printStackTrace()が例外がどこから来たのかを正確に示す完全なトレースを出力します。メッセージだけでは原因を見つけられないときに使いましょう。
すべてではなく、具体的な型をキャッチする
catchブロックは、宣言した型(またはそのサブクラス)に一致する例外だけをキャッチします。初心者の最大の間違いは、問題を「消す」ためにExceptionをキャッチすることです。
// これはやめましょう - 本当のバグを隠します
try {
doWork();
} catch (Exception e) {
// NullPointerException、タイプミス、ロジックの誤り... すべてを飲み込む
}
実際に処理できるもっとも狭い型をキャッチしましょう。不正な数値入力を想定しているならNumberFormatExceptionをキャッチします。想定していないものは伝播させて、壊れた状態のまま黙って進み続けるのではなく、実際にそれに気づけるようにすべきです。
複数の例外型を処理する
複数のcatchブロックを重ねられます。Javaは上から下へチェックし、最初に一致したものを実行するので、もっとも具体的なものからもっとも一般的なものへと並べましょう。
2つの型が同じ処理を共有する場合は、ブロックを複製する代わりに|を使った1つのマルチキャッチを使いましょう。
落とし穴: より一般的な型は、具体的な型のあとに来なければなりません。catch (Exception e)を最初に置くと、あとに続くより具体的なブロックが到達不能になり、コンパイラはそれを拒否します。
finallyは必ず実行される
finallyブロックは、成功、キャッチされた例外、あるいは早期のreturnであろうと、何が起きたかに関わらずtryとあらゆるcatchのあとに実行されます。必ず行わなければならない後始末はここに置きます。
例外が発生してもしなくても、"Closing resource"は出力されます。ただし、finallyの中からreturnするのは避けましょう。例外を黙って捨てたり、tryブロックから返された値を上書きしたりすることがあります。
try-with-resources は後始末を肩代わりしてくれる
ファイル、ネットワーク接続、データベースのステートメントなど、閉じる必要があるものを扱うとき、それをtry (...)の中で宣言すると、例外が投げられた場合でも、ブロックが終わるときに自動的に閉じられます。AutoCloseableを実装したあらゆる型で機能します。
これはtryで開いてfinallyで閉じる従来のパターンを置き換えるもので、閉じ忘れることがないためエラーが起きにくくなります。閉じられるリソースを扱うときは、いつでもこちらを選びましょう。
通常の制御フローに例外を使わない
try-catchは通常の条件ではなく、例外的な状況のためのものです。例外をキャッチするのは単純なチェックより高コストで、コードを読みづらくします。先に条件をテストできるなら、そうしましょう。
// 避ける: キーが無いことを調べるために catch を使う
try {
process(map.get(key).trim());
} catch (NullPointerException e) {
// キーが無い場合を処理
}
// 推奨: 明示的にチェックする
String value = map.get(key);
if (value != null) {
process(value.trim());
}
catchは、本当に自分の制御の外にあるもの、つまり不正なユーザー入力、無いファイル、ネットワーク障害のために取っておきましょう。
次へ: NullPointerException
Javaであなたがキャッチする(そして引き起こす)もっとも一般的な例外はNullPointerExceptionです。nullだったものに対してメソッドを呼び出した瞬間に現れます。次は、それを正確に何が引き起こすのか、スタックトレースの読み方、そしてそもそも発生させないための習慣を掘り下げていきます。
よくある質問
Javaでtry-catchはどう使いますか?
例外を投げる可能性のあるコードをtryブロックの中に入れ、続けて処理したい例外の型を指定するcatchブロックを追加します: try { risky(); } catch (IOException e) { ... }。try内のコードが一致する例外を投げると、Javaはクラッシュせずに直接catchブロックへ飛びます。変数(e)には例外オブジェクトが入っているので、e.getMessage()でそのメッセージを読み取れます。
Javaのfinallyブロックは何のためにありますか?
finallyブロックは、コードが成功しても、例外を投げても、さらには早期にreturnしても、何があってもtry/catchのあとに実行されます。ファイルを閉じる、ロックを解放するなど、必ず行うべき後始末の場所です。リソースを閉じる場合は、自動的に閉じてくれるtry-with-resourcesのほうがたいてい簡潔です。
Exceptionをキャッチすべきか、特定の例外型をキャッチすべきか?
実際に処理できるもっとも具体的な型をキャッチしましょう。Exception(さらに悪いことにThrowable)をキャッチすると、NullPointerExceptionのようなバグを含むあらゆる問題を飲み込んでしまい、本当の原因を隠します。期待しているのがそれならNumberFormatExceptionをキャッチし、想定外の例外は伝播させて気づけるようにしましょう。