目次 | |

11. 例外

Javaプログラムが,Java言語の意味制約に違反するとき,Java仮想計算機は,このエラーを例外(exception)としてプログラムに知らせる。 それらの違反の例として,配列境界の外でのインデクス付けがある。 あるプログラミング言語及びその処理系には,プログラムを強制的に終了することによって,それらのエラーに対応するものがある。他のプログラム言語では,処理系が任意又は予測不能な方法で対応することを許しているものもある。 これらの方法のいずれも,Javaの設計目標,移植性及び頑健性とは適合しない。 代わりに,Javaは,意味制約に違反したとき,例外を投げ,例外が発生した点からプログラマが指定可能な点への非局所的な制御の移動を引き起こすことを規定している。 例外は,それが発生した点から投げられる(thrown)と言い,制御が移動する点で捕捉される(caught)と言う。

Javaプログラムは,throw(14.16)を使用して,明示的に例外を投げることもできる。 これは,普通なら負の値を期待しないところで整数値-1のような予期しない値を返し,エラー条件を処理する古風な方式の代替手段を提供する。 経験的に,その予期しない値は,呼び出し元で無視されるか又は検査されないことが多いことが知られており,頑健的でない若しくは望ましくない振舞い又はその両方をプログラムにもたらす。

すべての例外は,クラスThrowableのインスタンス又はそのサブクラスの一つによって表現される。 それらのオブジェクトは,例外が発生する点からそれを捕捉するハンドラへと情報を運ぶために使用される。 ハンドラは,try(14.18)catch節によって構築される。 例外を投げる過程において,Java仮想計算機は,現在のスレッド内で開始はしたが完了していない,任意の式,文,メソッド及びコンストラクタ呼出し,静的初期化子並びにフィールド初期化式を,一つずつ中途完了する。 この処理は,その例外のクラス又はその例外のクラスのスーパクラスの名前を指定することによってその特定の例外を扱うことを示すハンドラを見つけるまで続けられる。 該当するハンドラが見つからない場合,メソッドuncaughtException(20.21.31)を現在のスレッドの親となるThreadGroupに対して呼び出す。 この方法によって,例外が処理されないままにしないためのあらゆる努力がなされる。

Javaの例外機構は,synchronized(14.17)及びsynchronizedメソッド(8.4.3.515.11)の呼び出しが中途完了したときに,ロックが解放されされるように,Javaの同期モデル(17)と統合されている。

例外(11.1)の様々な原因を11.では規定する。 例外をコンパイル時(11.2)に検査する方法及び実行時(11.3)に処理する方法を詳しく述べる。 詳細な例(11.4)を示し,その後,例外階層及び標準例外クラス(11.5)を規定する。

11.1 例外の原因

例外は,次の三つの理由(reasons)の内の一つによって投げられる。

例外は,クラスThrowableのインスタンス及びそのサブクラスのインスタンスによって表現される。 これらのクラスをまとめて,例外クラス(exception classes)とする。

11.2 コンパイル時の例外検査

Java言語は,メソッド又はコンストラクタの実行からどの検査例外が結果として返るかを,コンパイル時に分析することによって,Javaプログラムが検査例外(checked exceptions)用のハンドラを含んでいることを確認する。 メソッド(8.4.4)又はコンストラクタ(8.6.4)に対するthrows節は,結果となり得る各検査例外について,その例外クラス又はその例外クラスのスーパクラスの一つを言及しておかなければならない。 例外ハンドラの有無についての,このコンパイル時検査は,適切に扱われない例外の数を減らすために,設計されている。

非検査例外クラス(unchecked exceptions classes)は,クラスRuntimeException及びそのサブクラス,並びにクラスError及びそのサブクラスとする。他のすべての例外クラスは,検査例外クラス(checked exceptions classes)とする。 標準のJava APIは,検査及び非検査のいくつかの例外クラスを定義している。 検査及び非検査の付加的例外クラスが,Javaプログラマによって宣言されてもよい。 標準のJava API及びJava仮想計算機が定義しているJava例外クラスの階層及び例外クラスの規定に関しては,11.5を参照のこと。

throws節内で指定された検査例外は,メソッド又はコンストラクタの実装者及び利用者との間の契約の一部とする。 上書きするメソッドのthrows節は,上書きされるメソッドがそのthrows節で投げることを許されていない検査例外を,このメソッドが投げることを指定してはならない。 インタフェースが関係するときには,一つ以上のメソッド宣言が一つの上書き宣言によって上書きされてもよい。 この場合,上書きする宣言は,すべて(all)の上書きされる宣言(9.4)と互換性のあるthrows節をもたなければならない。

フィールドに対する変数初期化子(8.3.2)及び静的初期化子(8.5)は,検査例外を生じてはならない。 もし生じる場合,コンパイル時エラーが発生する。

11.2.1 エラーを検査しない理由

エラークラス(error classes) (Error及びそのサブクラス)である非検査例外クラスは,コンパイル時に検査されない。 その理由は,それらのエラーは,プログラム内の多くの点で発生可能であり,かつ回復が困難又は不可能なためである。 そのような例外を宣言しているJavaプログラムは,乱雑で要領を得ないものとなる。

11.2.2 実行時例外を検査しない理由

実行時例外のクラス(runtime exception classesRuntimeException及びそのサブクラス)は,コンパイル時に検査されない。その理由は,そのような例外を宣言させることは,Javaプログラムの正当性の確立を大して援助しない,とJava設計者が判断したためである。 Java言語の演算及び構文の多くは,実行時例外を生じることができる。 Javaコンパイラが利用できる情報及びコンパイラが実行する分析のレベルは,通常,たとえJavaプログラマには明白であるとしても,それらの実行時例外が起こり得ないと証明するためには十分でない。 そのような例外クラスの宣言を要求することは,単にJavaプログラマを苛立たせるだけだと考えられる。

例えば,あるコードが,その構造によって,決して空参照を含むことがない循環的なデータ構造を実装したとする。このとき,プログラマは,NullPointerExceptionは,決して起こらないと確信できる。しかし,コンパイラがそれを証明するのは,困難であろう。データ構造のそのような大域的な特性を確立するために必要な定理証明技術は,このJava言語規定の範囲を超える。

11.3 例外の取扱い

例外が投げられると,例外を引き起こしたコードから,その例外を取り扱う動的に囲まれた直近のtry(14.18)catch節へ制御が移される。

文又は式は, その文若しくは式が,catch節を一部とするtry文のtryブロック内に出現する場合,又はその文若しくは式の呼出し元が,catch節によって動的に囲まれている場合に,catch節によって動的に囲まれる(dynamically enclosed)という。

文又は式の呼出し元(caller)は,それが発生した場所に依存する。

特定のcatch節が例外を取り扱う(handles)かどうかは,投げられたオブジェクトのクラス及びcatch節のパラメタの宣言された型とを比較することによって決定する。catch節は,もしそのパラメタの型が,その例外のクラス又はその例外のクラスのスーパクラスならば,その例外を取り扱う。同様に,catch節は,宣言されたパラメタ型のinstanceof(15.19.2)である任意の例外オブジェクトを捕捉する。

例外が投げられるときに発生する制御移動は,例外を扱えるcatch節に出会うまで,式(15.5)及び文(14.1)の中途完了を引き起こす。その後,そのcatch節のブロックを実行することで,処理を続行する。例外を引き起こしたコードは,決して再開されないものとする。

例外を取り扱うcatch節を見い出せなければ,現在のスレッド(例外に遭遇したスレッド)を終了する,しかしその前に,すべてのfinally節が実行され,さらに現在のスレッドの親であるThreadGroupに対して,メソッドuncaughtException(20.21.31)が呼び出される。

たとえ他のコードのブロックが中途完了しても,一つのコードのブロックを確実に順次実行することが望ましい状況では,finally(14.18.2)を伴ったtry文を使用することができる。 try-finally文又はtry-catch-finally文内のtry又はcatchブロックが中途完了した場合,たとえ対応するcatch節が見つからなくても,例外の伝播中にfinally節が実行される。 finally節が,tryブロックの中途完了のために実行され,しかもfinally節自身も中途完了した場合,tryブロックの中途完了の理由は破棄され,新しい中途完了の理由がそこから伝播される。

中途完了及び例外捕捉のための正確な規則は,各文の規定と共に14.で,式に対しては,15.(特に15.5)で詳細に規定する。

11.3.1 例外の正確性

Javaの例外は,正確(precise)とする。 つまり,制御の転送が発生したとき,その例外が投げられた点より前に実行された文及び評価された式のすべての効果は,実行済みであるように見えなければならない。 例外が投げられた点の後に出現する文又はその一部は,評価されたように見えてはならない。 最適化コードが,例外が発生する点に続く式又は文のいくつかを先行して投機的に実行していれば,そのような最適化コードは,Javaプログラムの利用者に見える状態からこの投機的な実行を隠すように準備しておかなければならない。

11.3.2 非同期な例外処理

Javaのほとんどの例外は,それが発生するスレッドの動作の結果として同期的に発生し,その場所は,そのような例外を生じ得ると指定されたJavaプログラム内のある場所である。 これに対して,非同期な例外は,Javaプログラム実行中の任意の点で潜在的に発生できるものとする。

Javaでの非同期例外は少なく,次の結果としてだけ発生する。

メソッドstopは,一つのスレッドから,他のスレッド又は指定されたスレッドグループ内のすべてのスレッドに影響するように呼び出されてもよい。 他のスレッドの任意の実行点で発生できるので,非同期的とする。 InternalErrorは,これから規定するメソッドstopを処理するのと同じ機構を使用して取り扱えるので,同様に非同期とする。

Javaでは,非同期な例外が投げられる前に,少量で限られた量の処理を実行することができる。 この遅延は,最適化されたコードが,Javaの意味規則に従いながら,それらの例外を取り扱うのに実際的な場所で,例外を検出し投げることができるようにするために許している。

単純な実装では,各制御転送命令の位置で,非同期例外用のポーリングを行うかもしれない。 Javaプログラムの大きさは,限られているために,これは,非同期例外の検出における全遅延の限界をもたらす。 制御転送の間では,いかなる非同期的例外も発生しないので,コード生成器は,より高い性能のために実行する制御転送間での計算順序の並べ替えに多少の柔軟性をもつ。

参考
Mark Feeley による論文,Polling Efficiently on Stock Hardware, Proc. 1993 Conference on Functional Programming and Computer Architecture, Copenhagen, Denmark, pp.179-187, をもっと詳しい参考資料として推薦する。

すべての例外と同様に,非同期例外は正確とする(11.3.1)

11.4 例外の例

次に例を示す。


class TestException extends Exception {

TestException() { super(); }

TestException(String s) { super(s); }
} class Test { public static void main(String[] args) { for (int i = 0; i < args.length; i++) {

			try {
			thrower(args[i]);
			System.out.println("Test \"" + args[i] +
				"\" didn't throw an exception");
		} catch (Exception e) {
			System.out.println("Test \"" + args[i] +
				"\" threw a " + e.getClass() +
				"\n        with message: " + e.getMessage());
			}
		}
	}

static int thrower(String s) throws TestException { try { if (s.equals("divide")) { int i = 0; return i/i; } if (s.equals("null")) { s = null; return s.length(); } if (s.equals("test")) throw new TestException("Test message"); return 0; } finally { System.out.println("[thrower(\"" + s + "\") done]"); } } }
引数を,

divide null not test
として,プログラムを実行すれば,次を出力する。


[thrower("divide") done]
Test "divide" threw a class
 java.lang.ArithmeticException
        with message: / by zero
[thrower("null") done]
Test "null" threw a class java.lang.NullPointerException
        with message: null
[thrower("not") done]
Test "not" didn't throw an exception
[thrower("test") done]
Test "test" threw a class TestException
        with message: Test message
この例は,例外クラスTestExceptionを宣言する。クラスTestのメソッドmainは,4回メソッドthrowerを呼び出し,4回のうち3回例外を投げる。メソッドmain内のtry文は,throwerが投げる各例外を捕捉する。throwerの呼出しが正常完了するか中途完了するかにかかわらず,起ったことを記述するメッセージを印刷する。

メソッドthrowerの宣言は,検査例外クラス(11.2)TestExceptionのインスタンスを投げることができるので,throws節をもたなければならない。throws節を省略すると,コンパイル時エラーが発生する。

各呼出しに対して発生している出力 "[thrower(...) done]"が示すとおり,finally節は,例外が発生するかどうかにかかわらず,throwerのすべての呼出しで実行されていることに注意すること。

11.5 例外階層

Javaプログラムで発生し得る例外は,クラスObjectの直接のサブクラスであるThrowable11.520.22)をルートとするクラス階層で組織化されている。 クラスException及びクラスErrorは,Throwableのサブクラスとする。 クラスRuntimeExceptionは,Exceptionの直接のサブクラスとする。

標準パッケージ,java.langjava.utiljava.io及びjava.netで宣言されている例外クラスは,標準例外クラス(standard exception class)と呼ぶ。

Javaプログラムは,throw文で既存の例外クラスを使用できる,又は,Throwable若しくはそのサブクラスのいずれかとして,付加的な例外クラスを適宜定義可能とする。 例外ハンドラに対するJavaコンパイル時検査の利点を得るために,大部分の新しい例外クラスは,検査例外クラスとして定義することが一般的である,特にRuntimeExceptionのサブクラスでないExceptionのサブクラスとして定義する。

11.5.1 クラスException及びクラスRuntimeException

クラスExceptionは,例外からの回復を希望する普通のプログラムのためのすべての例外のスーパクラスとする。

11.5.1.1 標準実行時例外

クラスRuntimeExceptionは,クラスExceptionのサブクラスとする。 RuntimeExceptionのサブクラスは,非検査例外クラスとする。

パッケージjava.langは,次の標準実行時例外を定義する。これらは,パッケージjava.lang内の他のすべてのクラスのように,暗黙的にインポートされ,したがって単純名で参照可能とする。

パッケージjava.utilは,次の付加的な標準非検査実行時例外を定義している。

11.5.1.2 標準検査例外

RuntimeException以外のExceptionの標準サブクラスは,すべて検査例外クラスとする。

パッケージjava.langは,次の標準例外を定義する。これらは,パッケージjava.lang内のすべての他のクラスと同様に,暗黙的にインポートされ,したがって単純名によって参照可能とする。

パッケージjava.ioは,次の標準例外を定義している。

標準パッケージjava.netは,次の付加的なjava.io.IOExceptionのサブクラスを定義している。

11.5.2 クラスError

クラスError及びその標準サブクラスは,通常,普通のプログラムが回復を期待しない例外とする。クラスErrorは,クラス階層内のExceptionとは異なる,Throwableの別のサブクラスとする。プログラムは,回復が可能かもしれないすべての例外を捕捉するために,次の形式を使用できる。

} catch (Exception e) {
この記述は,回復が可能でないエラーを捕捉することはない。

パッケージjava.langは,ここで規定されるすべてのエラークラスを定義している。これらのクラスは,パッケージjava.lang内のすべての他のクラスと同様に,暗黙的にインポートされ,したがって単純名で参照可能とする。

11.5.2.1 ロードエラー及びリンクエラー

Java仮想計算機は,ロード,リンク,準備,検証及び初期化のエラーが発生するとき,LinkageErrorのサブクラスのインスタンスであるオブジェクトを投げる。

11.5.2.2 仮想計算機エラー

Java仮想計算機は,内部エラー又は資源制限によって,Java言語のセマンティクスを実装不可能なとき,クラスVirtualMachineErrorのサブクラスのインスタンスであるオブジェクトを投げる。この言語規定及びJava仮想計算機規定は,次の仮想計算機エラーを定義している。

洗練されたJavaプログラムは,OutOfMemoryErrorを取り扱って,回復を図る設計が,多分,オブジェクトの参照を注意深く無効にすることによって,なされることになろう。


目次 | |