try-catch で例外をつかむ
1 件おかしいだけで、残りの 999 件が処理されない
3 桁の英字とハイフンで始まる伝票コードを、まとめて処理するプログラムを書いたとします。ところが 1 件だけ形式の違うデータが混じっていて、そこで止まってしまいました。
Java
String[] codes = {"AB-123", "XY-999", "ZZ", "CD-456"};
for (String code : codes) {
System.out.println(code.substring(3));
}3 件目の "ZZ" は 3 文字目がないので、substring(3) が StringIndexOutOfBoundsException を投げます。この瞬間にプログラムは止まり、4 件目の "CD-456" は永久に処理されません。おかしいのは 1 件なのに、正しい 3 件まで巻き添えになるわけです。
Java は、こういう「想定外の事態」を例外という名前のオブジェクトで表します。何も対処しないと、例外が起きたところで処理が打ち切られ、そのまま外に飛び出していきます。
try に入れて、catch で受け止める
飛び出していく例外を途中で受け止めるのが try-catch です。try に「失敗するかもしれない処理」、catch に「失敗したときの後始末」を書きます。
Java
for (String code : codes) {
try {
System.out.println(code.substring(3));
} catch (StringIndexOutOfBoundsException e) {
System.out.println(code + " は形式が違うので飛ばします");
}
}これで 4 件すべてが最後まで処理されます。"ZZ" のところでは catch に飛び、メッセージを出したあと、次の周回に進みます。落ちずに続けられるようになったわけです。
catch (StringIndexOutOfBoundsException e) は「この型の例外を e という名前で受け取る」という意味です。e.getMessage() で原因の文字列、e.printStackTrace() で発生箇所の一覧が取り出せます。
例外の型は上下関係で並んでいます。頂点が Throwable、その下が Error と Exception の 2 本、さらに Exception の下に RuntimeException があります。Error は JVM 自体の異常なのでアプリ側では捕まえません。ふだん相手にするのは RuntimeException から下です。
catch の中に何を書くか
catch に入ったあと何をするかは設計の判断です。よく使うのは次の 2 つです。
- ログに残す。
e.printStackTrace()や業務ログへの出力で、あとから原因を追えるようにする - 代わりの値で続ける。失敗したら決められた既定値を使い、処理全体は止めない
上の例は 2 つ目に近い形です。読み取れなかった 1 件を飛ばして、残りを最後まで走らせています。
catch の型はできるだけ狭く指定してください。catch (Exception e) と書くと確かに何でも捕まりますが、本当は直すべき別のバグまで一緒に握り込んでしまいます。
よくある間違い
いちばん危険なのは、catch ブロックを空にする書き方です。
Java
try {
riskyWork();
} catch (Exception e) {
// 何もしない
}これを書くと例外が完全に消えます。画面にもログにも何も出ないまま、データだけが少しずつ壊れていきます。半年後に気づいたときには、原因を追う手がかりが 1 つも残っていません。何もすることがないときでも、最低限ログには残してください。
要件
tryブロックの中でInteger.parseInt(s)を呼び、その戻り値をreturnすることcatch (NumberFormatException e)で例外を捕まえ、catchブロックの中で0を返すことcatch (Exception e)のような広すぎる型ではなく、NumberFormatExceptionをピンポイントで指定すること
入出力例
parseOrZero("42") → 42
parseOrZero("abc") → 0
parseOrZero("") → 0
parseOrZero("-5") → -5
parseOrZero("100") → 100
parseOrZero("3.14") → 0