オブジェクト指向プログラミングb 第4回


●例外「もっとJavaを学ぶ 第2回 (2003年5月 )」

【エラー処理の課題】
 プログラムにとって必要不可欠でありながら,扱いが難しいもののひとつが,エラー処理である。
どのようにエラーを回復するか,という点については,それぞれのエラーごとに対応が違うので,
ここではふれない。ここでは,エラー処理を行うためのメカニズムについての課題を考えてみよう。

●エラー処理記述の複雑さ
 エラー処理を行う場合,もっとも原始的な方法は,エラーが起こってないかどうかをif文でチェッ
クし,エラーが起こっていない場合は通常の処理を行い,エラーが起こっていた場合は,else節でエ
ラー処理を行うという方法である。
 たとえば,List 1のように,4つの処理を実行している場合を考えてみよう。この例では,処理1か
ら処理4まで,順序よく処理が記述されていて(List 1-(1)),処理の流れが明確である。


 しかし,この例では,エラー処理は一切行っていない。そこで,List 1の各処理ごとに
  ・エラーが起こっていないかどうかの検査
  ・エラーが起こっていた場合のエラー処理
を追加した例が,下のList 2である。
 処理1〜処理4が行われている部分(List 2-(1))は,List 1-(1)と比べて,非常に見にくいものにな
っている。これは,エラー検査を行うif-else文で各処理が分断されているからである。そして,エラ
ー処理を行っているList 2-(2)の部分も,どこでどのエラーを処理しているか非常に分かりづらくな
っている。
 このように,if文でエラーの検査を行う古典的な手法は,本来の処理の記述を複雑にしてしまうと
いう問題をかかえていると言える。


ところが,今回学習する「例外」の仕組みを使うと,下図右のように
通常処理とエラー処理部分が綺麗に分離されて分かりやすくプログラム
が書けるようになるのである。
use_exception

 


●どこでエラー処理を行うか
 エラーが起こるのは,プログラムを実行しているときである。つまり,メソッドを実行しているとき
である。今ここで,あるメソッドを実行中に,エラーが起こったとしよう。このとき,そのメソッドで
完全にエラー対処を行うことができるかというと,かならずしもそうではない。場合によっては,呼び
出し元のメソッドにエラー処理を頼まなくてはならない場合もある。
 例として,メソッドm1( )がメソッドm2( )を呼び,さらにメソッドm2( )がメソッドm3( )を呼んでい
る状況を考えてみよう。たとえば,

class A {
    void m1( ) { m2( ); }
    void m2( ) { m3( ); }
    void m3( ) {}
}
というような場合である。下図Fig.1は,この状況でm3( )でエラーが起こった様子を示している。m3( )は,
起こったエラーに対して完全な処理はできず,できるだけのことをして,エラーが起こったことを呼び
出し元のm2( )に通知して,m2( )にエラー処理をまかせる。
 m3( )からエラー処理をまかされたメソッドm2( )も自分ではエラー処理を完全に行うことはできない
ので,呼び出し元のメソッドm1( )にエラーを通知して,エラー処理をm1( )にまかせる。メソッドm1( )
は,受け取ったエラー通知にしたがってエラーを処理することになる。


 このように,エラーが発生した場所とエラーを処理する場所は,異なっている場合がある。このよう
なケースに対処するためには,エラーを呼び出し元に通知する手段が必要になる。

 


●メソッドの返値をエラー通知に使う場合の欠点
 古典的な手法では,下図List 3のようにメソッドの返り値をエラー通知に使う。List 3-(1)ではエラー
内容を表すint型定数(エラーコード)を定義しており,メソッドm3( )は,エラーが起こると,エラーコード
aErrorを返す(List 3-(2))。m3( )を呼んでいるm2( )は,m3( )の返すエラーコードを調べて(List 3-(3)),
エラーが発生していたら,できるだけのエラー処理をしたら,エラーコードを呼び出し元に返す(List 3-(4))。
同様に,m1( )は,m2( )を呼び出しているところで,返されたエラーコードを調べて(List 3-(5)),エ
ラーが起こっていたら,エラー処理を行う(List 3-(6))。


 このように,メソッドの返り値を使ってエラーを呼び出し元に通知する古典的な方法には,以下のよ
うな欠点がある。
 ・第1に,メソッドの返り値をもっぱらエラー通知に使用することになってしまうこと。
   ※返り値には,エラーコード以外の値を返さなければならないメソッドもたくさんる。
 ・第2に,前述したように,メソッドの返り値を常にif文で調べなければならないので,処理の記述が複雑になってしまう
 ・第3に,エラーの内容を詳しく使えるのが難しい
   ※List 3の例では,整数値でエラーの内容を伝えることができるが,より詳しいエラー
    の情報(たとえば,エラーが起こった状況などに関する情報)が欲しい場合には,整数
    定数では不可能である。詳しいエラー情報を得るためには,エラーとして返す値をオ
    ブジェクトにすれば解決する。しかし,エラーが起こったときはあたりまえとして,
    エラーが起こらなかったときも,オブジェクトをいちいちを生成して返すのでは,実
    行効率が悪くなる。
 以上のように,古典的なエラー処理の方法は,様々な欠点を持ってる。このような欠点を解消したエラー
通知とエラー処理の方法,それがJavaが備えている例外処理である。次に,例外処理メカニズムの概要を説
明する。


【例外処理の基礎】
 例外(exception)とは,文字通り,「例外的な出来事」,すなわちエラーのことである。Javaの持つ例外
処理メカニズムでは,処理の実行中にエラーが発生した場合,エラーの内容を表す例外オブジェクトと呼ば
れるオブジェクトを,エラー通知に使う。
 この例外オブジェクトを使ってエラーの通知をすることを,“例外を投げる(スロー,throw)”と言う。
例外オブジェクトは,エラーを伝える通信筒と思えばよい。

●例外オブジェクトの型
 例外オブジェクトは,java.langパッケージに用意された,特別なクラスThrowableのサブクラスのオブジェ
クトである。下図Fig.2に,例外オブジェクトのクラスの継承関係を示す。Throwableは,すべての例外クラ
スのスーパークラスである。



 Throwableには,ErrorExceptionの2つのサブクラスが定義されている。また,Exceptionには,RuntimeException
というサブクラスが用意されている。ErrorとRuntimeExceptionは,Java仮想マシンが通知してくる特別な
例外を表している

 つまり,一般のプログラマは,Java APIで用意されているExceptionのサブクラスを使うか,Exceptionの
サブクラスを自分で定義するかして使うようにする


●例外を投げるthrow文
 実際に例外を通知する(=例外を投げる)には,throw文(下図Fig.3-(1))を使用する。たとえば,Exception
のサブクラスMyExceptionを定義してあるとして,このMyException型の例外オブジェクトを“投げる”には,
  throw new MyException( );
などとする。
 例外が投げられると,処理はそこで中断される。そして,エラーを処理できるところが,その例外オブジェクト
を受け取って(キャッチ,catch)
,処理を再開し,エラー処理を行う。イメージ的には,エラーが起こった部分で
エラーの内容を記したボール(例外オブジェクト)を,「誰かエラー処理してね」と言って,投げるような様子を想
像するとよいだろう。


●tryブロック
 例外処理の構文を,Fig.3-(2)に示す。形式的には,予約語のついたブロックがつらなった形になっている。
最初のブロックがtryブロックである(Fig.3-(2)<①>)。このtryブロックの中に通常の処理を書いておく。つまり,
この部分は通常の処理を“試す(try)”部分なのである。この通常の処理の中でエラーが起きた場合,例外を
“投げる”ことになる。

●catchブロック
 tryブロックに続くのが,catchブロックである(Fig.3-(2)<2>)。tryブロックで投げられた例外オブジェクト
は,このcatchブロックで“受け取る”ことができる
。catchブロックは,返り値なしのメソッドのようなもので,
  catch( MyException e ) {}
というように,例外オブジェクトを受け取るための仮引数を1個だけ持つことができる(ここで,MyExceptionは,
Exceptionのサブクラスとする)。そして,受け取った例外オブジェクトを参照するなどして,エラー処理を行う。
 catchブロックは,0個以上,繰り返して複数書くことが可能である。たとえば,
  try { throw new MyException( ); /* MyException型オブジェクトを投げる */ }
  catch( MyException me ) {}
  catch( Exception e ) {}
という具合である。
 このように,複数のcatchブロックがある場合,最初のcatchブロックから順序よく見ていって,投げられた
例外オブジェクトの型と一番最初に一致したcatchブロックが選択される
。上の例では,1番目と2番目のcatch
ブロックの仮引数型は,それぞれ,MyException,Exceptionとなっている。
 投げられた例外オブジェクトは,MyException型なので,型だけ見れば,MyExceptionもExceptionも一致す
る(MyExceptionが,Exceptionのサブクラスであるため)。しかし,1番目のcatchブロックの方が先に一致する
ので,実際には,1番目のcatchブロックが選択されることになる。そのため,1番目のcatchブロックの仮引数
meがtryブロックで投げられた例外オブジェクトを参照するようになり,1番目のcatchブロックの冒頭から処理
が再開される。
 ちょっと注意しなければならないのは,
 

try { }
catch( Exception e ) { }
catch( MyException me ) { } // このcatchブロックは絶対に実行されない

というように,スーパークラス型の仮引数を持つcatchブロックが,サブクラス型の仮引数を持つcatchブロック
より前に位置する場合である。このとき,第2のcatchブロックは絶対に選択されない。なぜなら,その前のスー
パークラス型の仮引数を持つcatchブロックが,必ず先に選択されるからである。このように,選択されないcatch
ブロックがあると,Javaコンパイラはエラーを通知する。

●finallyブロック
 さて,3種類のブロックのうち一番後ろに書くのが,finallyブロックである。finallyブロックは,省略することも
できる。また,2個以上は書けない。tryブロックから始まる構文が実行されると,tryブロックで例外が投げられた
かどうかに関係なく,必ず最後にfinallyブロックの中身を実行するようになっている

 よく一連の処理には,
  (1)前処理
  (2)中心的な作業
  (3)後処理
という形をとるものがある。たとえば,ファイルを扱う処理は,
  (1)ファイルのオープン
  (2)ファイルの読み書き
  (3)ファイルのクローズ
という形になっていて,オープンとクローズがそれぞれ前処理,後処理に相当する。この例は,ファイルというリ
ソース(資源)を扱うケースであるが,一般に,リソースを扱う処理は,
  (1)リソースの獲得
  (2)リソースに対する処理
  (3)リソースの解放
という手順をふむことが多い。ここで注目したいのは,たいていの場合,(3)の後処理は,(2)の作業でエラーが起こ
ったか起こらないかに関係なく,実行しなくてはならない,ということである。
 finallyブロックは,例外が発生したかどうかにかかわらず実行しなくてはならない後処理を記述するのに役立つ
たとえば,
try{
	(1)前処理
	(2)中心的な作業
}
catch( 例外型 仮引数 ) {
	エラー処理
}
finally {
	(3)後処理
}

と書いておけば,finallyの中に書かれた(3)の後処理は,例外が起こったかどうかに関係なく,実行されることになる。


●例外発生時の処理の流れ
 基本的な例外処理の流れを,下図Fig.3-(3),(4)の例でおさらいしてみよう。ここで,MyExceptionとYourException
は,Exceptionクラスの直接のサブクラスであるとする(Fig.3-(3)<①>)。
 最初にFig.3-(3)の例を見てみよう。まず,tryブロックの中の処理1を実行し,次にMyException型の例外オブジェクト
を投げる。すると,処理はそこで中断される。つまり,処理2は実行されない(Fig.3-(3)<2>)。
 次に,MyException型の例外オブジェクトをキャッチできるcatchブロックが探されることになる。最初に見つかるのは,
2番目のcatchブロックである。そのため,2番目のcatchブロックの仮引数meが投げられた例外オブジェクトを参照するよ
うに設定し,2番目のcatchブロックの先頭から処理が再開される(Fig.3-(3)<3>)。
 (2番目の)catchブロックの処理が終了すると,finallyブロックがあるので,finallyブロックに処理が移り,後処理を実行
する(Fig.3-(3)<4>)。
 finallyブロックの処理を終えると,try-catch-finallyブロックの次の文からまた実行されていく(Fig.3-(3)<5>)。



 次に,Fig.3-(4)の例を見てみよう。この例では,tryブロック内で例外が投げられず,処理1から処理4まですべて実行すること
になる(Fig.3-(4)<①>)。例外が投げられなかったので,tryブロック終了後,すぐにfinallyブロックに処理が移る(Fig.3-(4)<2>)。
そして,finallyブロックの処理が実行された後,try-catch-finallyブロックの次の文からまた実行されていく(Fig.3-(4)<3>)。

●キャッチされなかった例外

 ところで,上図Fig.3-(5)の例を見てみよう。tryブロックの中で,MyException型オブジェクトが投げられている(Fig.3-(5)
<①>)。しかし,MyException型例外をキャッチできるcatchブロックが存在しない。しかたないので,例外オブジェクトは投げ
られっぱなしのまま,finallyブロックの内容を実行する。
 問題なのは,その後である。finallyブロック内の後処理部分は実行したものの,例外はキャッチされないままの状態である。
つまり,エラー処理は行われていないのである(finallyブロックは,エラーが起こったかどうかに関係なく行われる共通の後処理部
分であって,エラー処理を担っているのではないことに注意)。
 このように,例外がcatchブロックでキャッチされない状況はよく起こる。たとえば,

try{  }
catch( MyException me ) {

	ここで,できるだけのエラー処理を行う
	throw me; // 再び例外を投げる

}

というように,catchブロックの中で再び例外を投げるような場合も,その新たに投げられた例外をキャッチするcatchブロック
は存在しない。finallyブロックで例外が投げられる場合についても同様のことが言える。
 こうした場合,強制的にtry-catch-finallyブロックの次の文,たとえばFig.3-(5)<3>から実行を強制的に再開すべきだろうか?
もちろん,エラー処理が行われていないのに,実行を再開しても,正常な処理が行われることは期待できないので,実行を無理矢
理再開することはしてはならない。では,どこから処理を再開できるのだろうか? そして,例外は投げられっぱなしになったま
まになるのだろうか?
 以上の例のように,例外がcatchブロックでキャッチされなかった場合,例外はさらにtry-catch-finallyブロックの「外」へ出て
行く
。これを,「例外の伝播(でんぱ)」と呼ぶ。もしその外側に別のtryブロックが有れば,伝播した例外をキャッチする機会が与えられる
 たとえば,Fig.3-(5)のプログラム部分の外側にさらにtry-catch-finallyブロックで囲んだものが,Fig.4-(1)である。処理の流れ
は,内側のtryブロックの中で例外が投げられ(Fig.4-(1)<2>),その例外をキャッチできるcatchブロックが無い場合は,finallyブ
ロックを実行(Fig.3-(1)<3>)した後,例外は外側のtryブロックへと出て行く(例外の?`?d)。ここで,内側のtry-catch-finalluブロッ
クの次に続く処理は実行されずにスキップされる(Fig.3-(1)<4>)ことに注意。
 外側のtryブロックに?`?dされた例外は,外側のcatchブロックでキャッチできないか調べられることになる。この例では,外側
のcatchブロックでキャッチできるので,外側のcatchブロックで例外がキャッチされて,エラー処理が行われることになる(Fig.4-(1)<5>)。
その後,finallyブロックに処理が移り(Fig.4-(1)<6>),それが終わると,外側のtry-catch-finallyブロックを終了して,次の処理を
行っていく(Fig.4-(1)<7>)。


 同様に,try-catch-finallyブロックが入れ子になっている場合,内側のcatchブロックで例外が投げられても,外側のcatchブロッ
クで例外をキャッチすることが可能である(Fig.4-(2))。まず,内側のtryブロックで例外が投げられ(Fig.4-(2)<①>),対応するcatch
ブロックで例外がキャッチされ,さらに例外が投げられる(Fig.4-(2)<2>)。すると,finallyブロックの後処理が行われ(Fig.4-(2)
<3>),外側のtryブロックへ例外が?`?dする。このとき,内側のtry-catch-finallyブロックに続く処理はスキップされる(Fig.4-(2)
<4>)。そして,外側のcatchブロックで例外がキャッチされ(Fig.4-(2)<5>),ついでfinallyブロックの内容を実行して(Fig.4-(2)
<6>),外側のtry-catch-finallyブロックに続く処理が実行されていく(Fig.4-(2)<7>)。
 このように,try-catch-finallyブロックが入れ子になっていて,外側のcatchブロックで例外をキャッチできればいいのだが,や
はり
void m( ) {

	try {
		throw new MyException( ); // ここで投げられる MyException型例外をキャッチできるcatchブロックは,このメソッド内には無い!
	}
	catch( YourException ye ) { } 

}

というように,メソッド内で例外をキャッチできるcatchブロックが存在しない場合はある。また,そもそもtryブロックで囲まれていない範囲から例外が
投げられる
void m( ) {

	throw new MyException( );   // 例外射出! でもここはtryブロックの内側じゃない!

}

というような場合も,このメソッド内で例外をキャッチできない。このようなケースでは,投げられっぱなしの例外はどこでキャッ
チされて,どこでエラー処理がされるのだろうか。


【メソッド外への例外の伝播】
 実は,メソッドの中で例外をキャッチできない場合,メソッドはその例外を,そのメソッドを呼び出しているメソッドへと投げるので
ある。これが,まさにFig.1で示したメソッド間のエラー通知である。つまり,Javaではメソッドが自分で例外を処理しきれない場合,例
外を呼び出しもとのメソッドに投げることによって,エラーを通知することができるのである。
 ただし,メソッドが例外を投げるためには,メソッドがどのような型の例外を投げるかを,メソッド宣言のthrows節に加えておかなけ
ればならない。その書式を,Fig.5に示す。書き方は簡単で,( )で囲まれた仮引数リストの後ろに,予約語throwsを書いて, そのメソッド
が投げる可能性のある例外型をカンマで区切って並べるだけである。


 もし,例外を投げる可能性のあるメソッドが,適切にthrows節を書いていないと,コンパイル時にエラーになる。たとえば,

class A {

	void m( ) throws E1 { }

	void h( ) { // エラー!
		m( );
	}

}

という場合,メソッドm( )はE1型例外を投げる可能性があると宣言されているので,m( )を呼び出しているメソッドh( )もE1型例外を投げ
る可能性がある。したがって,このままでは,h( )の宣言はエラーになってしまう。そのため,h( )の宣言でもthrows節をつけて,
class A {

	void m( ) throws E1 { }

	void h( ) throws E1 { // OK!
		m( );
	}

}

というように,宣言しなくてはならない。
 もし,例外をキャッチできるcatch節がある場合,メソッドがその例外を呼び出し元に投げることはないので,
class A {

	void m( ) throws E1 { }

	void h( ) {  // OK! 
		try {
			m( );
		}
		catch( E1 e ) {
			ここでエラー処理   // E1型例外はここでキャッチされて,もうメソッドh( )から出て行くことは無い!
		}
	}
 
}


という場合のh( )では,throws節をつけなくてもエラーにならない。


 なお,例外クラスE1,E2が例外クラスEのサブクラスであるとき,

void m( ) throws E1, E2 {

	// E1型, E2型を投げるかもしれない処理

}

というメソッド宣言のthrows節を,スーパークラスEを使って
void m( ) throws E {

	// E1型, E2型を投げるかもしれない処理

}

と書くこともでるが,これはあまり良いことではない
。このメソッドm( )がどのような例外を投げる可能性があるのかが,あやふやに
なってしまうからである。スーパークラスを使ってまとめて書いてしまうようなことはせずに,throws節には,できるだけ詳細に例外
型を明示するべき
である。
 なぜ,このようにメソッドが投げる例外の型を,throws節で明示しておく必要があるのだろうか。それは,必ず例外をキャッチする
ようにするためである。下図List 4は,<①>で例外が投げられているが,これはキャッチされない。実際に実行してみると,List 4-<①>
で例外が投げられた後,プログラムが強制終了してしまうことがわかる。つまり,投げられた例外がキャッチされない場合,プログラム
は強制的に終了してしまう
のである。
 そのため,プログラムを作成するときは,投げられた例外を確実にキャッチしなくてはならない。そのためには,どのメソッドがどの
ような例外を投げるかがはっきりと分かるようにしておかなければならない。これが,throws節でそのメソッドが投げる例外の型を明示
する理由である。
List 4 Uncaught.java



●チェックされない例外
 ところで,throws節に書かなくてもコンパイルエラーにならない特殊な例外型もある。それは,Error と Runtime およびそれらのサブ
クラス
である。そのためこれらは,(throws節で)チェックがされない例外と呼ばれる(Fig.2)。
 Error は,Java仮想マシンで重大な実行時エラーが起こったことを表している。たとえば,クラスファイルが壊れているような場合な
どである。また,メモリ不足で新しいオブジェクトを生成することにnew演算子が失敗したときなどに投げられる OutOfMemoryError
Error のサブクラス VirtualMachineError のサブクラスである。
 こういったエラーは,予想外の場所で起こりえるし,一般のアプリケーションがエラー回復できるような種類のものではない。そのた
め,通常は,Errorおよびそのサブクラス型の例外をキャッチしようとしてはならない。こういった理由で,特にthrows節に明記する必要
は無いようになっているのである。
 また,RuntimeException は,Java仮想マシンの実行中に起こる軽度のエラーで,Javaのプログラムでは,どこでも起こりえるような
エラーを表す。たとえば,

A a = null;
a.m( );

というように,nullオブジェクトに対してメソッドを呼び出そうとした場合に投げられる NullPointerException型例外や,配列の範囲外
を参照しようとすると投げられる IndexOutOfBoundsException型例外などが RuntimeException のサブクラスである。これらの例外は,
多くはアルゴリズムを見直すことで解決できる。
 RuntimeException は,前述したとおり,頻繁にどこでも起こりえる例外で,言い換えれば,どのメソッドも投げる可能性のある例外
である。つまり,メソッドのthrows節にいちいち書くのは,冗長になってしまう。そのため,throws節に書かなくてもよいようになって
いるのである。
 Error と RuntimeException およびそのサブクラスは,Javaのシステムが投げる例外である。そのため,プログラマはこれらのサブクラ
スを自分で作成して利用することはしてはいけない。通常は,Java APIで定義されている Exception のサブクラスを使うか,自分で
Exception のサブクラスを作って使用することになる。


【実際の使用例】
  では,復習として,実際に例外を使用しているプログラム例を見てみよう(下図List 5)。List 5-<①>では,4つの例外クラス
E1〜E4を,Exceptionのサブクラスとして定義している。
 クラスAの中では,処理1〜処理4を行うメソッドm1( )〜m4( )と,後処理を行うメソッドpost( )が定義されている(List 5-<2>〜<6>)。
m1( )は,フィールドiの値が1だったらE1型例外を投げる。同様に。m2( )は,iが2だったらE2型例外を投げる,という具合にm4( )まで
定義されている。
 クラスExceptionExample1のメソッドh( )は,仮引数でA型メンバオブジェクトaのフィールドiを初期化している。そして,List 5-<7>
が処理の中心で,tryブロックの中で,処理1〜処理4までを実行するために,メソッドm1( )〜m4( )が順序よく呼ばれてる。
 その後ろに,E1〜E4型の例外をキャッチするそれぞれのcatchブロックがあり(List 5-<8>〜<11>),最後に,finallyブロックで後処理
を行うpost( )メソッドが呼ばれている(List 5-<12>)。
 main( )メソッドでは,メソッドh( )に実引数0〜4を渡して呼び出している(List 5-<13>〜<17>)。List 5-<13>の呼び出し
  h( 0 );
では,例外は投げられないので,表示結果は
  process #1
  process #2
  process #3
  process #4
  postprocess
  ----------
となり,処理1〜処理4と後処理が実行されることがわかる。List 5-<14>の呼び出し
  h( 1 );
では,メソッドm1( )を呼んだときにE1型例外が投げられ,List 5-<8>のcatchブロックでキャッチされるので,表示結果は
  process #1
  E1
  postprocess
  ----------
となる。実際にList 5を実行してみて,List 5-<15>〜<17>の表示結果がどうなるかも確認してみよ。
List 5 ExceptionExample1.java



【まとめ】
 ここで,例外の利点をまとめてみよう。
 ・通常の処理の記述がシンプルで読みやすくなる
   ※上図List 5-<7>の部分は,正規の処理が整然と並んでいて,どのように処理が進んでいくか一目瞭然である。
    これは,古典的な手法を使ったList 2-<①>の読みにくさに比べると雲泥の差である。また,エラー処理部分も,
    List 2-<2>では,どの部分がどのエラーを処理しているのかひと目では分からないが,List 5-<8>〜<11>で
    は,どのcacthブロックが,どの例外を処理しているかは明確にわかる。
 ・例外を使うと,メソッドの返り値を自由に使用することができ,例外オブジェクトが生成されて投げられるのは,
  エラーが起こったときだけ

   ※これらの点は,メソッドの返り値をエラー通知に使用する方法に比べて,格段に優れている。
 ・オブジェクトという高度な情報体をエラー通知に使えるので,様々なエラー処理の方法を考えることができる
   ※たとえば,Javaの例外クラスが,Fig.2のように継承関係を使って系統的に組織化されているように,自分で
    エラーの種類を系統的に分類することも可能である。

 このように,古典的なエラー処理に比べて,例外処理は様々な利点を持っている。しかし,古典的なエラー処理(
例えば,エラーコードを返値として返すメソッドなど)にも,例外処理の仕組みに比べて単純であるという強みがある。
したがって,例外処理を主なエラー処理として使いつつ,必要に応じて古典的な方法も利用すると良いだろう。

◆JavaのAPIドキュメントでは,例外を投げる可能性があるメソッドについては,投げられる例外についても明示され
 ている。JavaAPIのメソッドを利用するときには,例外についてもよく確認して,それらのメソッドから投げられる
 可能性のある例外を,確実にキャッチできるようにプログラムを書くこと。


◆山崎先生による演習問題

【問題】実際にプログラムの実行時にエラーを起こしてみる。
data.txtという名前のファイルを入力用にオープンするには
FileInputStream in=new FileInputStream("data.txt") ;
で行なえる。(詳しくは入出力の回で説明する。)
ファイルが存在するかどうかは実行する時にしかわからない。
List 6 ExceptionExample2.java


【問題 1】ExceptionExample2.javaをコンパイルして実行してみよ。
このとき実行がどのような順序でされるか 行番号で答えよ。(行番号10の実行から書け)
【問題 2】次に同じフォルダにdata.txtという名前のファイルをエディターで作ってから実行してみよ。
このとき実行がどのような順序でされるか 行番号で答えよ。(行番号10の実行から書け)

List 7 ExceptionExample3.java


【問題 3】data.txtを消去せよ。ExceptionExample3.javaをコンパイルして実行してみよ。
このとき実行がどのような順序でされるか 行番号で答えよ。(行番号15の実行から書け)
【問題 4】次に行番号20の//を消してからコンパイルして実行してみよ。 実行画面に現れたメッセージを下の行から読んでいけ。
at ExceptionExample3.f(ExceptionExample3.java:7)
at ExceptionExample3.main(ExceptionExample3.java:15)
の意味を推測せよ。printStackTrace( ) メソッドはエラーの原因を見つけるのに 便利なメソッドである。

Exception       一般的例外クラス
IOException      入出力の例外クラス
FileNotFoundException ファイルが見つからないという例外クラス
この例外はそれぞれ上の例外を継承している。(下の例外程具体的な例外を表す)
List 8 ExceptionExample4.java


【問題 5】ExceptionExample4.javaをコンパイルして実行してみよ。
このとき実行がどのような順序でされるか 行番号で答えよ。(行番号10の実行から書け)
【問題 6】例外をキャッチする順序は具体的なものから先に書かれている。
これを、 catch( Exception e ) { // 一般的例外を処理する
の行から先に書くとどうなるか?


戻る