第8章 例外処理
8.3 例外とクラスの関係について
これまでtry~catchで処理を行ってきた「例外」とは、クラスライブラリ(JDKに準備されているjava.langパッケージ)の中のThrowableクラスのサブクラスのオブジェクトのことを意味しています。(サブクラスとは、あるクラスの機能を受け継いだ新しいクラスのことです。)たとえば、これまでに扱った例外の中では、ArrayIndexOutOfBoundsExceptionやArithmeticExceptionというクラスのオブジェクトがそれに該当します。catchブロックではクラスを扱っていたため、()内でこのクラスのオブジェクトを受け取って扱うための変数を記述していたのです。
例外のオブジェクトを受け取ると、catchブロック内では変数eがその例外オブジェクトを指し示すようになります。
なお、受け取ることができる(catchブロック内に指定できる)例外オブジェクトは、「Throwable」クラスのサブクラスでなければなりません。では次の項で実際に、例外オブジェクトの情報がどのようなものかサンプルで紹介します。
8.3.1 例外オブジェクト情報を出力するサンプル
例外をわざと発生させ、その際に取得できる例外オブジェクトの情報を画面に出力します。
① ソース・フォルダー: myproj_basic/src
② パッケージ: jp.co.f1.basic.ch08
③ 名前: ExceptionOutPrint
④ 作成するメソッド・スタブの選択:public static void main(String[] args) にチェックを入れる
package jp.co.f1.basic.ch08; public class ExceptionOutPrint { public static void main(String[] args) { try { // int型配列を定義 int[] intArray = new int[5]; // 配列に値を代入 System.out.println("配列に数値を代入します。"); intArray[10] = 50; // 例外をわざと発生させる代入 // 結果を表示 System.out.println("配列に50を代入しました。"); } catch (ArrayIndexOutOfBoundsException e) { // 変数名eは例外オブジェクトの場所情報を受け取る System.out.println("配列の要素数を超えています。"); System.out.println(e + "という例外が発生しました。"); // 例外クラスの情報を表示 } finally { System.out.println("例外処理の最後の処理です。"); } System.out.println("処理終了"); } }
解説
15行目のcatchブロックにした例外オブジェクトのを受け取るための変数「e」を設定しています。
17行目でそのオブジェクト変数「e」の情報を画面に出力しています。
}catch(ArrayIndexOutOfBoundsException e){ //変数名eは例外オブジェクトの場所情報を受け取る System.out.println("配列の要素数を超えています。"); System.out.println(e + "という例外が発生しました。"); //例外クラスの情報を表示 }
実行結果を見て分かるように例外の情報が出力されています。この方法を利用すると、どんな種類の例外が起きたか詳細な情報を知ることができます。
8.3.2 例外の種類を知る
例外と言ってもその種類はたくさんあります。例外クラスの大元になっているThrowableクラスと、そのサブクラスは次のような関係になります。
図 8.3.1: 例外クラスの基本構造
例外関連のクラスは大きく分けると「Errorクラスに属するクラス」、「RuntimeExceptionクラスに属するクラス」、「それ以外のExceptionクラスに属するクラス」の3つに分類されます。
図8.3.1を見ると分かるように、例外クラスはツリー構造となっていて、大元になっている「Throwableクラス」から枝分かれして各例外クラスが存在しています。
まず初めに、「Throwableクラス」から「Errorクラス」と「Exceptionクラス」の2つに枝分かれします。「Exceptionクラスに属するクラス」は、クラス名の最後に必ずExceptionがつきます。「Errorクラスに属するクラス」と「Exceptionクラスに属するクラス」の区別は、このネーミングルールによって判断することができます。
また、「Exceptionクラスに属するクラス」も「RuntimeExceptionクラスに属するクラス」と「それ以外のExceptionクラスに属するクラス」に分けることができ、上記図で示したように「非チェック例外クラス」と「チェック例外クラス」の2つに分類されます。(どのような種類があるかは公式のWebページに目を通すことをお勧めします。)
例外の分類
例外には大きく分けてコンパイル時に、チェックされるものとされないものの2種類に分類できます。
1. 非チェック例外:例外処理を記述しなくてもコンパイルできる例外のこと
非チェック例外は、例外処理を記述することがあまり推奨されていません。なぜなら、これらの例外クラスを全て処理しようとすると、例外処理の記述量が膨大なものとなり、見苦しいプログラムになりがちだからです。
それらを差引いても処理する理由がある場合にのみ、任意の例外処理を記述します。
ただErrorクラスはメモリ不足など、プログラムが実行できないようなエラーを扱っていますので、例外処理は通常行いません。
2. チェック例外:例外処理を記述しないとコンパイルエラーになる例外のこと
チェック例外は例外が送出される可能性があるクラスのため、必ず例外処理を記述しなければなりません。もしも記述しない場合はコンパイルエラーになりますので注意が必要です。
8.3.3 例外クラスの上下関係について
これまでのサンプルではピンポイントで例外クラスをcatchブロックに記述してきましたが、どのような例外が発生するか分からない場合、上位クラスを指定するとそれに含まれるサブクラスの例外を全てcatchしてくれます。
例えばArrayIndexOutOfBoundsExceptionとArithmeticException場合は、「RuntimeException」でcatchすればどちらも処理してくれるようになります。さらに上位のExceptionを指定しても、問題なくその下位の例外を全て処理してくれます。
図 8.3.2: 上位クラスと下位クラスの例外処理の関係
凡例:上位クラスで例外をまとめて処理を行う
凡例で示したようにExceptionクラスでcatchブロック1つ記述していれば、その下位クラスのどのような例外が発生しても例外処理を行ってくれるようになります。但し発生した例外の種類によってその後の処理を変更したい場合は、これまで学習してきたようにピンポイントで例外クラスを指定する必要があります。上位クラスを使う場合は想定外のエラーに対処する場合に記述しましょう。
それでは次の項で上位の例外クラスを使用して、catchブロックをまとめたプログラムを紹介します。
8.3.4 上位の例外クラスを利用して全ての例外を処理するプログラム
上位の例外クラスを利用して、全ての例外クラスがcatchされ正しく処理されるか確認します。
① ソース・フォルダー: myproj_basic/src
② パッケージ: jp.co.f1.basic.ch08
③ 名前: OnlyException
④ 作成するメソッド・スタブの選択:public static void main(String[] args) にチェックを入れる
package jp.co.f1.basic.ch08; public class OnlyException { public static void main(String[] args) { try { // int型配列を定義 int[] intArray = new int[5]; // 配列に値を代入 System.out.println("配列に数値を代入します。"); intArray[10] = 50; // 例外をわざと発生させる代入 // intArray[0] = 50; //正しく代入する // 結果を表示 System.out.println("配列に50を代入しました。"); int num = intArray[0] / 0; // 例外をわざと発生させる0除算 System.out.println("割り算の結果は" + num + "です。"); } catch (Exception e) { System.out.println(e + "という例外が発生しました。"); } finally { System.out.println("例外処理の最後の処理です。"); } System.out.println("処理終了"); } }
実行結果
※配列エラーの例外が発生した場合(サンプルソースのまま実行してください)
※0除算の例外が発生した場合(11行目をコメントアウト、12行目のコメント解除をして実行してください)
解説
20行目のcatchブロックでExceptionを指定しています。Exceptionクラスをcatchブロック内に指定していればErrorクラスの例外以外は全て処理してくれます。実行結果から見ても分かるように、配列エラーの場合でも0除算を行っても正しく例外を処理しています。
}catch(Exception e){ System.out.println(e + "という例外が発生しました。"); }・・・
今回のサンプルのように上位の例外クラスをcatchブロックに指定すれば、その下位の例外クラスは全て処理してくれることが分かったと思います。どのような例外が発生するか分からない場合は、Exceptionクラスを指定してエラーの情報を画面に表示すれば処理が中断することもなくエラーの情報も取得できるようになります。
複数のcatchブロックと上位例外クラスの並び順(優先度)について例外処理はcatchブロックの並び順(上から下へ)で処理を行っていきます。catchブロックが複数記述してあり、下位の例外クラスより先に上位の例外クラスを記述してしまうとコンパイルエラーになってしまいます。
先に上位の例外クラスを指定すると、そのcatchブロックで例外が処理されてしまい下のcatchブロックに処理が到達することがなくなってしまうからです。
catchブロックを複数記述し、上位クラスを併用して記述する場合の並び順は必ず下位クラス→上位クラスの順で記述するようにしましょう。
8.3.5 例外処理追加時の注意点
例外処理を行うには例外が発生する処理に対して、tryブロックで囲む必要があります。そのブロック内で変数宣言していると、その変数はtryブロックの中でしか利用できない変数になってしまいます。
変数には、宣言した位置(ブロック)でのみ有効となる有効範囲(変数のスコープ)があります。これはtryブロックに限った話ではなくifブロック、whileブロック等の「{}」で囲まれた部分全てで共通です。
凡例:例外処理の追加における変数のスコープ注意
作成したプログラムに例外処理を追加したことでエラーが出たり、tryブロック内の処理で得た変数の値を利用したい場合はこの変数のスコープに注意して下さい。