プログラミング応用b 第6回『例外』

【例外処理の基礎】

 例外(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)②)。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)②)。

 次に,MyException型の例外オブジェクトをキャッチできるcatchブロックが探されることになる。最初に見つかるのは,
2番目のcatchブロックである。そのため,2番目のcatchブロックの仮引数meが投げられた例外オブジェクトを参照するよ
うに設定し,2番目のcatchブロックの先頭から処理が再開される(Fig.3-(3)③)。

 (2番目の)catchブロックの処理が終了すると,finallyブロックがあるので,finallyブロックに処理が移り,後処理を実行
する(Fig.3-(3)④)。

 finallyブロックの処理を終えると,try-catch-finallyブロックの次の文からまた実行されていく(Fig.3-(3)⑤)。





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

次に進む