前々回、前回で、コンパイル時と実行時に発生するエラー・例外をまとめてきた。
前々回の記事:
【備忘録】Javaにおける「コンパイルエラー」と「実行時エラー」 | Shino’s Mind Archive
前回の記事:
【備忘録】Javaの実行時に発生するエラーについて | Shino’s Mind Archive
そのうち、例外の中でもチェック例外については例外処理を書こうということをまとめた。
その例外処理とは何ぞやという部分を、今回はまとめていこうと思う。
なお、前々回から引き続き、今回も備忘録だ。
つまり、勉強しながらなので間違っている可能性も否定できない。
もしミスがあったら、ご指摘頂ければ幸いだ。
改めて例外処理とは
例外処理とは、ある例外が発生した場合に、代わりに行わせる処理のことだ。
例えば、あるメソッドの中で割り算をしたいとしよう。
割り算をするとき、通常であれば、0では割れない。
プログラムでも、0で割ろうとすると例外が発生する。
これはArithmeticExceptionというもので、非チェック例外だ。
非チェックなのだが…前回書いた通り、例外処理を書くこともできる。
というわけで、今回はこれを例に、例外処理を見ていこう。
なお、これも前回書いているが、非チェック例外は、基本的に起こさせないようコーディングするべきだ。
参考にするなら、あくまで例外処理の書き方という意味で参考にしてほしい。
もちろん、チェック例外でも書き方は全く同じになる。
繰り返しになるが、違いはこの例外処理を書かなければいけないかどうかだ。
例外処理なしの場合
先に、例外処理を使用しない場合の例を書いてみよう。
該当のメソッド部分のみ抜き出してみる。
private int div(int a, int b) {
int res = 0;
if(b != 0) {
res = a / b;
} else {
System.out.println("0割りが発生したため、0を返します。 at div()");
}
return res;
}
二つの数字a
、b
を受け取って、b
が0と異なる場合に割り算を行うようにしている。
つまり、ゼロ割である例外は発生しなくなる。
で、もし0だったら、コンソールにその旨を表示してから、0を返す。
こちらはそんなに難しくないと思う。
例外処理ありの場合
では、この処理を例外処理で書いてみよう。
先にソースコードを。
private int divException(int a, int b) {
int res = 0;
try {
res = a / b;
} catch(ArithmeticException e) {
System.out.println("0割りが発生したため、0を返します。 at divException()");
}
return res;
}
まず、最初に行っていたb
のチェックは行っていない。
つまり、実際の割り算の部分(4行目)で、例外が発生する可能性がある。
その、発生する可能性のある部分をtry
というもので囲む。
その後、catch(例外クラス 変数)
というブロックを付け加え、その中で例外が発生した場合の処理を書く。
こうすることで、例外処理というものが記述できる。
上のプログラムの動作を書くと…
- 戻り値用変数
res
を用意する。 res
にa/b
を代入する。- 2で例外(ゼロ割)が発生した場合、
catch
内の処理に移る。- エラー表示を行う。
res
を返す。
こうなる。
最終的に見ると、一つ目の例と同じ動作になっているのが分かるだろうか。
違いは、例外をそもそも発生させないようにするか、それとも発生したら対処するかになる。
これが、例外処理の基本的な書き方だ。
なお、catch
の小括弧に書く例外クラスというのは、今回で言えばArithmeticException、前の配列の要素範囲外であればArrayIndexOutOfBoundsExceptionなど、その例外を表すクラスが用意されている。
そのクラスの変数を受け取って処理を行えるので、発生する可能性のあるものを書こう。
ただし、このクラスは継承関係になっているものもあり、できる限り細かい指定をしておくことがポイントだ。
でないと、例えば例外全体を表すExceptionというクラスもあるのだが、これだとあらゆる例外を全て引き受けてしまう。
catch文は複数つなげることもでき、上から順番に評価されていく。
なので、細かい順にcatchしていき、やるなら最終的に全てを受け持つ、といった書き方にしよう。
例外発生の有無に関係なく実行する
さて、今回の例ではあまりないと思うが、例えばファイル読み込みの場合なんかは最後にファイルとの接続を切らなければいけない。
これは、例外が発生しようがしまいが行う処理だ。
このように、tryブロック内で例外が出たかどうかに関わらず行う処理も書ける。
それが、finallyというブロックだ。
書き方を見てみよう。
try{
// 例外が発生する可能性のある処理
} catch (例外クラス 変数名) {
// 例外が発生した場合に行う処理
} finally {
// 例外発生の有無に関わらず最終的に行う処理
}
こう書くことで…
- try内の処理
- 例外が発生したら2へ
- 例外が発生しなかったら3へ
- catch内の処理
- 終わったら3へ
- finally内の処理
という流れで処理を行うことができる。
ファイル読み込みの場合、このfinallyの中で接続を切るようにしよう。
なお、try内で例外が発生した瞬間に、それ以降のtryブロック内の処理は無視される。
これはfinallyがあろうとなかろうと共通した動作だ。
で、ここで注意したいポイントがある。
この各ブロック内でreturn文を書いた場合だ。
各ブロック内にreturn文があるときの動作
return文がある時は、ちょっと見た目とは変わった処理順になる。
finallyブロックがある場合で、それぞれの動作を見ていこう。
まず、tryブロック内、catchブロック内にreturn文がある場合。
このとき、まずは普通にtryの中身を実行する。
これでreturn文まで例外なく進んだ場合、finallyが先に実行されてからtry内のreturnが実行される。
また、例外が発生してcatch内の処理に進んだ場合、こちらも処理を進めていき、returnの前でfinallyの処理を行ってからreturnを行う。
つまり、returnがあっても、その直前でfinallyが必ず実行される。
で、そのfinallyの中にreturnがある場合だが…これは文法上は可能だが、やってはいけない。
なぜかというと、finallyは正常でも、例外が発生しても、必ず処理を行う。
つまり、その中でreturnをすると、例外が発生しても呼び出し元には一切分からなくなってしまうのだ。
eclipseで組んでいるとここに警告が出てくるので、それでやっていないかチェックしておこう。
コンソールの場合は警告が出ないので、要注意だ。
なお、try、catch内にreturnがない場合は、finallyまで終わった後、そのままその次の処理に進む。
逆に、両方ともreturnがある場合は、そのfinallyブロックより後の処理は到達不可能になる。
この時に処理を書いていると今度はコンパイルエラーが出るので注意してほしい。
ちょっとまとめてみよう。全部で4パターン。
一つ目、try、catch両方ともreturnがない場合。
try{
// 処理1
// 処理2 : 例外発生の可能性あり
// 処理3
} catch() {
// 処理4
} finally {
// 処理5
}
// 処理6
// return文
このとき、例外が発生しなければ、
- 処理1
- 処理2
- 処理3
- 処理5
- 処理6
- return文
という順番で、例外が発生すれば
- 処理1
- 処理2 : 例外発生!!
- 処理4
- 処理5
- 処理6
- return文
という順番になる。
二つ目、tryだけreturn文がある場合。
try{
// 処理1
// 処理2 : 例外発生の可能性あり
// 処理3
// return文1
} catch() {
// 処理4
} finally {
// 処理5
}
// 処理6
// return文2
例外が発生しない場合は、
- 処理1
- 処理2
- 処理3
- 処理5
- return文1
となり、例外が発生する場合は、
- 処理1
- 処理2 : 例外発生!!
- 処理4
- 処理5
- 処理6
- return文2
となる。
三つ目、catchブロックにのみreturnがある場合だ。
try{
// 処理1
// 処理2 : 例外発生の可能性あり
// 処理3
} catch() {
// 処理4
// return文1
} finally {
// 処理5
}
// 処理6
// return文2
そろそろ分かってきたのではないだろうか。
例外発生なしの場合は、
- 処理1
- 処理2
- 処理3
- 処理5
- 処理6
- return文2
となり、例外発生ありの場合は、
- 処理1
- 処理2 : 例外発生!!
- 処理4
- 処理5
- return文1
となる。
最後に、try、catch両方ともreturn文がある場合。
try{
// 処理1
// 処理2 : 例外発生の可能性あり
// 処理3
// return文1
} catch() {
// 処理4
// return文2
} finally {
// 処理5
}
// 処理6
// return文3
この場合、例外発生なしだと、
- 処理1
- 処理2
- 処理3
- 処理5
- return文1
となり、例外発生ありだと、
- 処理1
- 処理2 : 例外発生!!
- 処理4
- 処理5
- reutrn文2
となる。
このときは処理6、return文3は到達できないため、書いてはいけない。
この動き方をしっかり理解しておこう。
まとめ:例外処理の基本
今回は例外が発生した場合に行う例外処理をごく簡単にまとめてきた。
例外が発生する可能性のある部分をtryというブロックで囲む。
その後、対処する例外を受け取り、例外処理を行うcatchブロックを書いていく。
また、それら両方の最後に必ず行う処理をfinallyというブロックに書くことも可能だ。
その場合、try、catch内にreturn文があろうと、finallyの中身が必ず実行される。
このfinallyブロック内にreturnを書いてしまうと例外発生の有無に関係なく同じ値を返してしまうので、それは避けよう、という話だった。
さて、ここまでで元々Java側で用意されている例外を、発生したときに対処するようなものは書けるようになった。
しかし、ある程度組んでいると、発生したものは呼び出し元にそのまま通知したいとか、自分で新しい例外を作りたいとなってくる。
次回は、そのあたりを解説していこう。
更新情報はTwitter、Facebookにて告知している。
どちらも、ページ下部の各アイコンから確認できるので、よかったら覗いていってほしい。
それでは。
コメント