目次 | | | 索引 Java言語規定
第2版

11. 例外

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

プログラムは,throw文(14.17)を使用して,明示的に例外を投げることもできる。

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

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

Javaプラットフォームの例外機構は,synchronized文(14.18)及びsynchronizedメソッド(8.4.3.6, 15.12)の呼出しが中途完了したときに,ロックが解放されされるように,同期モデル(17)と統合されている。

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

11.1 例外の原因

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

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

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

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

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

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

静的初期化子(8.7),クラス変数初期化子,及びインスタンス初期化子又はインスタンス変数初期化子及びインタフェース(8.3.2)は,検査例外を生じてはならない。もし生じる場合,コンパイル時エラーが発生する。 インスタンス初期化子又は無名クラス(15.9.5)内のインスタンス変数初期化子へ適用される制約事項はない。

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

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

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

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

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

11.3 例外の取扱い

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

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

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

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

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

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

たとえ他のコードのブロックが中途完了しても,一つのコードのブロックを確実に順次実行することが望ましい状況では,finally節(14.19.2)を伴ったtry文を使用するとよい。

try-finally文又はtry-catch-finally文内のtry又はcatchブロックが中途完了した場合,たとえ対応するcatch節が見つからなくても,例外の伝播中にfinally節が実行される。 finally節が,tryブロックの中途完了のために実行され,しかもfinally節自身も中途完了した場合,tryブロックの中途完了の理由は破棄され,新しい中途完了の理由がそこから伝播される。

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

11.3.1 例外の正確性

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

11.3.2 非同期例外処理

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

高品質な機械コードを生成するために,非同期例外のセマンティクスの正しい理解が必要である。

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

stopメソッドは,一つのスレッドから,他のスレッド又は指定されたスレッドグループ内のすべてのスレッドに影響するように呼び出されてもよい。他のスレッド又はスレッドの任意の実行点で発生できるので,非同期的とする。 InternalErrorは,非同期と見なされる。

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 例外階層

プログラムで発生し得る例外は,クラスObjectの直接的下位クラスであるThrowable11.5)をルートとするクラス階層となる。クラスException及びクラスErrorは,Throwableの直接的下位クラスとする。クラスRuntimeExceptionは,Exceptionの直接的下位クラスとする。

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

クラスExceptionは,例外からの回復を希望する普通のプログラムのためのすべての例外の上位クラスとする。クラスRuntimeExceptionは,クラスExceptionの下位クラスとする。 RuntimeExceptionの下位クラスは,非検査例外クラスとする。RuntimeException以外のExceptionの下位クラスは,すべて検査例外クラスとする。

クラスError及びその下位クラスは,通常,普通のプログラムが回復を期待しない例外とする。例外階層の詳細についてはJava API規定を参照のこと。

クラスErrorは,Throwableの下位クラスで,Exceptionとは異なるとする。プログラムは,回復が可能かもしれないすべての例外を捕捉するために,次の形式を使用できる。

} catch (Exception e) {
この記述は,回復が通常は不可能なエラーを除き使用される。

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

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

11.5.2 仮想計算機エラー

Java仮想計算機は,内部エラー又は資源制限によって,Java言語のセマンティクスを実装不可能なとき,クラスVirtualMachineErrorの下位クラスのインスタンスであるオブジェクトを投げる。これらのエラーを定義を記述しているThe Java Virtual Machine Specification, Second Editionを参照のこと。

目次 | | | 索引 Java言語規定
第2版