Fujitsu The Possibilities are Infinite

 

COBOL技術者のためのJava言語入門
9章:例外処理

[索引]  例外の構文 |  例外処理の流れ |  チェック例外と非チェック例外 |  例外の階層構造 |  メソッド外での例外処理 |  独自例外のスロー 

Javaには例外処理という便利な機能があります。 例外はファイルの終了検出、エラーの発生、0除算の実行など、通常ではない特別の状態のときに発生します。 例外発生時に実行されるのが例外処理です。 例外処理を使うことによって、プログラムの構造がとてもすっきりしたものになります。 ここでは例外処理のごく基本的な内容を紹介します。

例外の構文

例外の構造

        try {
            例外監視の対象となる文(a)
        } catch(例外タイプA) {
            タイプAの例外を検出したときに実行する文(b)
        } catch(例外タイプB) {
            タイプBの例外を検出したときに実行する文(c)
        } finally {
            例外発生の有無にかかわらず必ず最後に実行する文(d)
        }

例外処理の基本的な構文を示します。
(a) tryブロック内に例外の発生する可能性のある文を記述する。
(b) 例外タイプを明記したcatchブロック内に、その例外発生時の処理を書く。
(c) catchブロックは複数書くことができる。
(d) finallyブロックには例外発生の有無にかかわらず最後に実行する処理を書く。

try、catch、finallyはこの順序で連続して記述する必要があります。 tryとcatchだけ、またはtryとfinallyだけでもかまいません。

例外処理の流れ ★

(1)例外が発生すると例外処理状態となり(2)へ行く。
(2)tryブロック内で起きていれば(3)へ、そうでなければ(4)へ行く。
(3)catchブロックのタイプを上から順に調べる。タイプがマッチすれば(6)へ行く。
     catchブロック群を最後まで探しても、マッチしなければ(4)へ行く。
     ただし、finallyブロックがあれば、(4)へ行く前にそれを実行する。
(4)自メソッドを呼び出した処理があれば、そこに戻って(2)から繰り返す。
     自メソッドを呼び出した処理がなければ(mainメソッドのとき)、(5)へ行く。
(5)システムの処理が行われ、プログラムは実行停止する。(終わり)
(6)例外処理状態は解除され、そのcatchブロック内の処理を実行して(7)へ行く。
(7)finallyブロックがあれば、それを実行してfinallyブロックの次の処理へ進む。
     finallyブロックがなければ、catchブロック群の直後の処理へ進む。

例外処理の例

実際のプログラムで例外発生時の処理の流れを見てみましょう。

  01:   public class Sample {
  02:     public static void main(String[] args) {      // メイン処理
  03:       System.out.println("main-start");
  04:       aaa();                                      // aaaを呼ぶ
  05:       System.out.println("main-end");
  06:     }

  07:     public static void aaa() {                    // aaaメソッド
  08:       try {
  09:         System.out.println("aaa-start");
  10:         bbb();                                    // bbbを呼ぶ
  11:         System.out.println("aaa-end");
  12:       } catch(ArithmeticException e) {            // 例外のcatch
  13:         System.out.println("aaa-catch");
  14:       } finally {                                 // finally文
  15:         System.out.println("aaa-final");
  16:       }
  17:     }

  18:     public static void bbb() {                    // bbbメソッド
  19:       System.out.println("bbb-start");
  20:       int x = 0;
  21:       int y = 10/x;                               // 例外発生!(0除算)
  22:       System.out.println("bbb-end");
  23:     }
  24:   }

このプログラムでは次のように処理が行われます。
(1) 03: main-startを出力
(2) 04: メソッドaaa()を呼ぶ。 07行目に移る
(3) 09: aaa-startを出力
(4) 10: メソッドbbb()を呼ぶ。 18行目に移る
(5) 19: bbb-startを出力
(6) 21: 0除算によりArithmeticException例外が発生する(例外処理状態になる)
(7) try/catchがないので、呼び出し元に戻る(例外処理状態のまま)
(8) 12: 対応する例外のcatchがあるのでそこに移る(例外処理状態解除)。 11行目は実行されない
(9) 13: aaa-catchを出力。 catchブロック終了
(10) 14: finallyブロックがあるのでそこに制御が移る
(11) 15: aaa-finalを出力。 finallyブロック終了
(12) メソッドaaa()が終わりなので、呼出元に復帰する
(13) 05: main-endを出力。 mainメソッド終了


【確認問題】 9.1

次のプログラムを実行すると何が出力されますか。

        public class Ex {
          public static void main(String[] args) {
            try {
               int a = 11/0;
               System.out.println("try");
            } catch(IndexOutOfBoundsException e) {
               System.out.println("指数範囲例外");
            } catch(ArithmeticException e) {
               System.out.println("算術例外");
            } finally {
               System.out.println("finally");
            }
          }
        }

チェック例外と非チェック例外

Javaにはプログラマーが必ずtry/catchしなければならない例外があります。 これをチェック例外、または検査例外といいます。 チェック例外は、例えば以下のように処理メソッドごとにcatchすべき例外のタイプまで決まっています。 チェック例外は、try/catchブロックを作りさえすれば、catchブロック内に記述する内容は自由です。 中括弧の中に何も書かなくてもかまいません。

        ファイルオープン時 … FileNotFoundException例外
        ファイル読み込み時 … IOException例外

チェック例外の例

以下にチェック例外の例を示します。 FileReaderのnew文ではFileNotFoundExceptionが発生する可能性があります。 この例外はチェック例外なので、try/catchしないとコンパイルエラーになります。

        public void fileOpen(String filename) {
          try {
            FileReader fr = new FileReader(filename);
          } catch(FileNotFoundException e) {
            System.out.println(e);
          }
        }

※ 例外処理の強要は、かなり厳しい規則ですが、質の高いプログラム作りに有効です。 しかしこの機能はJavaより新しいC#では導入されておらず、プログラム言語の必須機能として必ずしもコンセンサスが得られているわけではないのかも知れません。

非チェック(非検査)例外

一方、例外が発生する可能性はあるが、必ずしも例外処理を行う必要がないケースもあります。 それが非チェック例外です。 非チェック例外には、次の2種類があります。
(1) 例外処理を記述してもプログラムレベルでは回復できないような重大な例外
(2) あまねく発生するので、いちいちtry/catchするのが効率的でない例外

例えば、メモリ枯渇(OutOfMemoryError)のような重大なエラーはcatchしても回復しようがなく、結局プログラムを止めるしかありません。 したがって、このようなエラーではtry/catchの記述は強制されません。 これは上記(1)の例です。

宣言した範囲を越えて配列をアクセスするとArrayIndexOutOfBoundsExceptionという例外が発行されます(2章)。 この例外の発生に備えようとすると、配列を扱うすべての処理をtryブロックで囲まねばなりません。 しかも、範囲外のアクセスはもうプログラムのバグなので、例外処理などせずに、さっさと実行を止めた方がよいのです。 このような理由で、非チェック例外にすべき例外のグループがあります。 これは上記(2)の例です。

例外の階層構造

Javaでは例外もクラスを使って管理します。 すなわち、例外が発生すると、そのタイプごとに用意された例外クラスを使って例外を発行します。 例外クラスは継承を基にした階層構造をなしています。 以下に例外クラスの例を示します。

Throwableクラス ★

例外クラス全体のスーパークラスになっているのが、Throwableというクラスです。 Throwableクラスを直接継承しているクラスに、ErrorクラスとExceptionクラスがあります。 すなわち、以下のような構造になっています。

        Throwableクラス = Exception(例外)クラス + Error(エラー)クラス

例外クラスの親玉がExceptionではなく、Throwableであることに注意してください。 この章の説明内容にはExceptionクラスだけではなく、Errorクラスのことも含んでいます。 したがって、本章のタイトルは例外処理ではなく、Throwable処理とする方が適切かもしれません。 しかし、通常はErrorクラスの処理も含めて例外処理と呼んでいます。 例外という言葉はThrowableクラス相当の広い意味と、Exceptionクラス相当の狭い意味があると考えるべきでしょう。 ちなみに、Throwableは「投げることができる」と言う意味で後述のように(広義の)例外はthrowする(投げる)ものであるところから来ています。

Error系例外

Errorクラスを継承している例外をError系例外と言います。 Error系例外は回復不可能な重大なエラーで、非チェック例外の説明で(1)として挙げた例外に相当します。 OutOfMemoryErrorなどのように、名前の最後にErrorが付きます。 Error系例外は普通はtry/catchしません。 Error系例外には、メモリ不足を示すOutOfMemoryErrorや、スタックオーバーフローStackOverflowErrorなどがあります。

Exception系例外

Exceptionクラスを継承している例外をException系例外と言います。 Exception系の例外は回復可能な軽度のエラーです。 Exception系の例外には、名前の最後にExceptionが付きます。 この例外には、RuntimeExceptionクラスを継承している非チェック例外と、それ以外のチェック例外とがあります。 前者が非チェック例外の説明で(2)として挙げた例外に相当します。 すなわち、Exception系例外は以下のような構造になっています。

        Exceptionクラス = RuntimeExceptionとそのサブクラス(非チェック例外)
                + その他のExceptionクラスとそのサブクラス(チェック例外)

Exception系の非チェック例外には、0除算などで発生する算術例外ArithmeticException、nullが代入された参照を使おうとすると発生するNullPointerException、範囲外のインデックスを使って配列がアクセスされたことを示すArrayIndexOutOfBoundsExceptionなどがあります。 また、不適切な文字列を数値に変換しようとしたときに発生するNumberFormatExceptionも、なぜか非チェック例外です。

Exception系のチェック例外には、指定した外部ファイルが見つからないFileNotFoundExceptionや、ファイルの終端(End Of File)に達したときに出るEOFException、およびこれらのスーパークラスであるIOExceptionなどがあります。 また、呼び出そうとするクラスが存在しないことを示すClassNotFoundExceptionもチェック例外です。

catchブロックでの例外タイプ指定

catchブロックは例外タイプごとに記述すると説明しましたが、必ずしも細かいタイプをいちいち指定する必要はありません。 catchブロックでは例外の上位の階層のクラスを指定できるので、例えば以下のようにExceptionと指定すれば、Exceptionクラスを継承しているすべての例外をcatchできます。 その代わり、当然ですが詳細なタイプごとの処理はできません。

        catch(Exception e)

catchブロックで使う変数

catchブロックの丸括弧内では、例外タイプの他に変数名を指定します。 この変数名は任意の名前で良いのですが、普通 e を使います。 これは例外クラスのインスタンスを指しています。 これをprint文に与えると、その例外クラスを説明するメッセージを出力します。 例えば0除算の例外発生時、e には次のようなメッセージが入っています。 e は必要がないなら使わなくてかまいません。

        java.lang.ArithmeticException: / by zero

【確認問題】 9.2

非チェック例外になるクラスの最上位のクラスを2つ挙げてください。

メソッド外での例外処理

チェック例外はかならずtry/catchが必要ですが、その場でcatchしなくても、その責任を呼び出し元のメソッドに転嫁することができます。 それにはメソッド定義でthrows宣言をすればよいのです。 throws宣言は、その例外発生の可能性をプログラマーが認識していることを表明するものです。 throws宣言はまた、そのメソッドを呼び出す側で、try/catchして欲しいということも表わしています。

throwsの例

以下にthrows宣言の例を示します。 チェック例外のところで例示したfileOpenメソッドはtry/catchを必要とするFileReaderのnew文を含みますが、以下のように、メソッドのシグニチャでthrowsキーワードを記述し、その後ろに発生する可能性がある例外タイプを書くと、このメソッドでは例外をtry/catchする必要はありません。

        public void fileOpen(String filename) throws FileNotFoundException {
          FileReader fr = new FileReader(filename);
        }

発生する可能性がある例外が複数ある場合は、コンマで区切って列挙します。 またcatchブロックと同じように、上位の階層の例外クラスを使うこともできます。 Exceptionクラスを使えばすべての例外をthrowsすることができます。

throwsの引き継ぎ

throws宣言により、そのメソッドではcatchが不要になりましたが、今度はそれを呼び出すメソッドでtry/catchする(か、さらに上にthrowsする)義務が生じます。


【確認問題】 9.3

チェック例外対策としてプログラマーができることを2つ挙げてください。

独自例外のスロー

プログラマーが独自の例外クラスを定義して、それを発行することができます。 独自の例外クラスはThrowableクラスかそのサブクラスを継承して作ります。 普通はExceptionクラスを継承します。 以下に独自の例外クラスの定義例を示します。 ただ例外を発生させるためだけなら、以下のように { } 内に何も記述する必要はありません。

        class MyException extends Exception { }

この例外の発行はthrow文を使って次のように行います。 throwキーワードはthrowsキーワードと似ていますが、別物です。

        throw new MyException();