Previous | Next | Trail Map | Writing Java Programs | 例外を使用したエラー処理


catch ブロック

前のページで学習したように、 try 文はその関連した例外ハンドラの適用範囲を定義する。 直接 try ブロックの後に1つ以上の catch ブロックを用意することによって、例外ハンドラをtry 文と結び付ける。

try {
    . . . 
} catch ( . . . ) {
    . . . 
} catch ( . . . ) {
    . . . 
} . . .
 

try 文 の終わりと最初の catch 文の初めの間に介在するコードはない。 Java の catch 文の一般的な書式は次のようになる。

catch (SomeThrowableObject variableName) {
    Java statements
}

例からわかるように、 catch 文はひとつの仮引数を必要とする。catch 文への引数は、メソッドのための引数宣言のように見える。 引数の型 SomeThrowableObject は、ハンドラが処理する例外の型を宣言し, java.langパッケージで定義したThrowableクラスから継承されたクラス名でなくてはならない。 ( Java プログラムが例外をあげる時、Java プログラムは実際にはオブジェクトをあげている、そしてThrowable から派生するオブジェクトだけをあげることができる。 例外をあげる方法で例外をあげることについてさらに学習する。)

variableName は、ハンドラがキャッチした例外を参照するときに使用する名前である。 例えば、それぞれの writeList() メソッドの例外ハンドラ(後で示す)はそれぞれ,例外の宣言名 e を使用して getMessage() メソッドを呼び出す。

e.getMessage() 

オブジェクトのインスタンス変数や他のメソッドをアクセスするのと同じ方法で、例外のインスタンス変数やメソッドをアクセスする。getMessage() は、Throwable クラスによって用意されるメソッドで,発生したエラーについての追加情報を出力する 。Throwable クラスは、例外が発生した時、実行スタックの内容をで塗りつぶして、プリントする2つのメソッドも実装する。 Throwable のサブクラスは、他のメソッドやインスタンス変数を追加することができる。 例外が何のメソッドを実装するか見つけだすためには、そのクラス定義とその祖先クラスの定義を確認する。

catch ブロックは一連の正当な Java の文を含む。 これらの文は、例外ハンドラが起動される場合に実行される。 ランタイムシステムは、あげられた例外の型と一致する型を持つハンドラが呼び出しスタックで最初のものになると、例外ハンドラを起動する。

ListOfNumbers クラスからのwriteList() メソッドは、その try 文 に対して2つの例外ハンドラを使用する。try ブロック内であげられる例外 ArrayIndexOutOfBoundsException と IOException の2つの型それぞれには、ハンドラがついている。

try {
    . . . 
} catch (ArrayIndexOutOfBoundsException e) {
    System.err.println("Caught ArrayIndexOutOfBoundsException: " + e.getMessage());
} catch (IOException e) {
    System.err.println("Caught IOException: " + e.getMessage());
}

IOException の発生

IOException が try ブロック内で発生すると仮定する。 ランタイムシステムはすぐに発進して、適切な例外ハンドラを探す。 ランタイムシステムはメソッド呼び出しスタックの一番上でその検索を始める。 例外が発生した時、 FileOutputStream コンストラクタは呼び出しスタックの一番上にあった。 しかし、 FileOutputStream コンストラクタは適切な例外ハンドラを持っていないので、ランタイムシステムは、メソッド呼び出しスタックの次のメソッドであるwriteList() メソッドを確認する。 writeList() メソッドは、IOException と ArrayIndexOutOfBoundsException の2つの例外ハンドラを持っている。

ランタイムシステムは writeList のハンドラをそれらが try 文の後に続いて現われる順序で確認する。 例外ハンドラの引数が,あげられた例外の引数と一致する最初の例外ハンドラが、例外を処理するためにランタイムシステムが選択する例外ハンドラである。 (例外ハンドラの順序は重要である!) 最初の例外ハンドラの引数は ArrayIndexOutOfBoundsException であるが、あげられた例外は IOException である。 IOException が ArrayIndexOutOfBoundsException に割り当てられないので、ランタイムシステムは適切な例外ハンドラを検索し続ける。

writeList() の2番目の例外ハンドラの引数は IOException である。 FileOutputStream コンストラクタによってあげられた例外も、IOException なので、ハンドラの IOException 引数に割り当てられることができる。 したがって、このハンドラは適切であり、ランタイムシステムはこのハンドラを実行し、そして次の文を出力する。

Caught IOException: OutFile.txt 

ランタイムシステムは、ArrayIndexOutOfBoundsException が発生した場合も同様の処理を行う。 詳細については、 一緒にすべてを置く で、 writeList() メソッドが完了した後のこのメソッド(もう1つ手続がある)を通して、3つのシナリオの間に何が起きるかを調査する。

1つのハンドラによる複数の例外型のキャッチ

writeList() メソッドが使用する2つの例外ハンドラは非常に専門的である。 それぞれが1つの型の例外だけを処理する。 Java 言語では、複数の型の例外を処理する汎用的な例外ハンドラを作成することができる。

知ってのとおり、 Java 例外は Throwable オブジェクトである(それらは Throwable のインスタンス、あるいは Throwable のサブクラスである)。 Java パッケージは Throwable から派生する多数のクラスを含んでいるので、 Throwable クラスの階層を構築する。

独自の例外ハンドラを記述することにより、 Throwable から継承されるどんなクラスでも処理できる。 「リーフ」クラス(サブクラスがないクラス)のハンドラを書く場合は、そのクラス用の特別なハンドラを作成する。それは、この特定の型の例外だけを処理する。 「ノード」クラス(サブクラス持っているクラス)のハンドラを書く場合は、汎用的なハンドラを作成する。それは、ノードクラスとそのサブクラスの型を持つすべての例外を処理する。

もう一度 writeList() メソッドを変更してみる。 今回は、IOExceptions と ArrayIndexOutOfBoundsExceptions の両方を処理するように作成する。 IOException と ArrayIndexOutOfBoundsException の最も近い共通の祖先は、Exception クラスになる。 したがって両方の型の例外を処理する例外ハンドラは、次のようになる。

try {
    . . .
} catch (Exception e) {
    System.err.println("Exception caught: " + e.getMessage());
}

Exception クラスは Throwable クラス階層の中でかなり高いところに位置する。 それで、この例外ハンドラがキャッチしようとする IOException と ArrayIndexOutOfBoundsException の型に加えて、他の多くの型をキャッチする。 一般的に言って、例外ハンドラはより専門的にするべきである。 ハンドラは(最善の回復方策を判定するために)、どんな例外型が発生するかを判定しなければならないので、例外の全部やほとんどの例外をキャッチするハンドラは、エラーの回復には一般的には役立たない。 同様に、あまりにも汎用的な例外ハンドラは、プログラマが予期していなかったり、ハンドラが意図しなかった例外のキャッチや処理を行うことにより、コードはさらにエラーを発生しやすくなる。


Previous | Next | Trail Map | Writing Java Programs | 例外を使用したエラー処理