例外

例外の構文 try, catch, finally

Javaでは、プログラムの実行中にエラーが起こったときに、例外クラス(Throwableインターフェースを実装したもの)を投げる(throw)ことがあります。例外がthrowされると、プログラムの流れがとまり、try文中なら、その外側のcatchクローズによって例外が補足されます。

tryブロック中を正常に終了したときも、例外処理を行った後も、finallyクローズ内のコードは必ず1度実行されます。特に必要としない場合は、finally節を省略してもよいです。

try {
  // throws IOExceptionという宣言をもつメソッドを実行
} 
catch(IOException e) {
  // IOExceptionが発生したときの処理
  System.err.println(e.getMessage());
}
finally {
  // この部分のコードはtry、あるいはcatchのブロックの後、必ず1度だけ実行される	
}

例外はプログラムをわかりやすくする

たとえば、ファイルを開いてデータをプログラムで読むことを考えます。ファイルを開く手順は以下のようになるでしょう。

ファイルを開く
ファイルのサイズを調べる
ファイルの大きさに応じたメモリを確保する
ファイルのデータをメモリに読む
ファイルを閉じる

この手順をプログラムにするときには、

ファイルが開けなかったら?
ファイルのサイズが調べられなかったら?
十分なメモリを確保できなかったら?
ファイルの読み込みが失敗したら?
ファイルを閉じられなかったら?

と様々なエラーが起こりえます。

これらのエラー処理を挟んだ擬似コードを書くと以下のようになるでしょう。(http://java.sun.com/docs/books/tutorial/essential/exceptions/advantages.html より引用、一部翻訳)

initialize errorCode = 0;

ファイルを開く
if (ファイルが開いている?) {
  ファイルサイズを調べる
    if (ファイルサイズはわかった?) {
	  メモリを確保
        if (十分なメモリがあるか?) {
	    ファイルのデータをメモリに読む
            if (ファイルの読み込めたか?) {
                errorCode = -1;
            }
        } else {
            errorCode = -2;
        }
    } else {
        errorCode = -3;
    }
    ファイルを閉じる
    if (ファイルは閉じられたか? && errorCode == 0) {
        errorCode = -4;
    } else {
        errorCode = errorCode and -4;
    }
} else {
    errorCode = -5;
}
return errorCode;

エラー処理のコードが、実行したいことの間にさし挟まって、一見しただけではどのような処理をしたいのかわかりません。これらのエラー処理のコードを例外として表現すると、コードは以下のように、処理の流れを邪魔せずにエラー処理のコードを書けるようになります。

try {
  ファイルを開く
  ファイルのサイズを調べる
  メモリを確保
    ファイルを読んでメモリに保存
  ファイルを閉じる
} catch (ファイルを開くのに失敗) {
   doSomething;
} catch (ファイルの大きさを調べるのに失敗) {
    doSomething;
} catch (メモリの確保に失敗) {
    doSomething;
} catch (ファイルの読み込みに失敗) {
    doSomething;
} catch (ファイルを閉じるのに失敗) {
    doSomething;
}

例外を生じうるメソッドの定義

例外を投げるクラスの場合、RuntimeException以外の例外(Exceptionクラスをベースとしたもの)をメソッド内で投げる可能性がある場合は、メソッド定義は以下のように書く必要があります。

IOExceptionが

public void someFunction() throws IOException {
   new URL("... ").openStream(); // openStreamは、IOExceptionを投げるかもしれない
  }

例外クラスの階層構造

throw, catchできる例外クラスはThrowable interfaceを実装しています。Javaでは例外の階層は、おおまかにExceptionと、RuntimeExceptionのどちらかを拡張して実装されたものになっています。

例外を投げる (throw)

例外は、

throw new 例外クラス(引数 エラーメッセージなど);

という形で発生させることができます。

RuntimeException

プログラム中であってはいけない状態(つまりcatchで補足して回復するのが難しい場合)を表現するのにRuntimeExceptionを拡張した例外クラスをよく使います。RuntimeExceptionのJavaDoc

使用例

throw new UnsupportedOperationException("まだ実装してません");

if(data == null)
   throw new NullPointerException("dataがnullです");

これらの例外をメソッド中で投げるときは、明示的にthrows NullPointerExceptionなどと書かなくてもよいことになっています。

自分の例外クラスを定義する

自分のプログラム特有の例外を定義するには、Exceptionクラスを拡張するようにします。

public class MyException extends Exception {

   public MyException(Throwable e) {
      super(e);
   }

   public MyException(String errorMessage) {
      super(errorMessage);
   }

   (他のコンストラクタ ...)
}

Exceptionのvariationはenumを使って表現する

昔のJavaの教科書でよく見受けられるのが、エラーの種類ごとにExceptionを拡張したクラスを作る方法ですが、これはお勧めできません。

public class InvalidDataException extends Exception { ... }

public class SomethingWrongOccurredException extends Exception { ... }

...

といった具合です。これは実装が同じで、名前が違うだけのjavaのファイルの数をいたずらに増やすだけになるので、あまりよい手法とは言えません。Exceptionの種類を増やしたいだけなら、以下のように

ErrorCode.java

public enum ErrorCode {
   INVALID_DATA,
   SOMETHING_WRONG_OCCURRED, 
   UNKNOWN_ERROR
}

とエラーの種類を表現するenum型を定義し、自分のExceptionクラスの中でErrorCodeを必ず受け取るようにコンストラクタを記述します。

MyException.java

public class MyException extends Exception {

   private ErrorCode errorCode;
 
   public MyException(ErrorCode errorCode, Throwable e) {
      super(e);
      this.errorCode = errorCode;
   }

   public MyException(ErrorCode errorCode, String errorMessage) {
      super(errorMessage);
      this.errorCode = errorCode;
   }

   (他のコンストラクタ ...)

   public ErrorCode getErrorCode() { return errorCode; }
}

このようにExceptionを拡張すると、ErrorCode内の要素を増やすだけで、表現したいエラーの種類を簡単に増やすことができます。

また、例外の補足も、

try {

} 
catch(InvalidDataException e) {
   //...
}
catch(SomethingWrongOccurred e) { 
   //... 
}

ではなく、

try {

} 
catch(MyException e) {
   switch(e.getErrorCode()) {		  
      case INVALID_DATA:
          //...
          break;
      case SOMETHING_WRONG_OCCURRED:
          // ....
          break;
      default:
          //....
          break;
   }
}

とエラー処理をしたいErrorCodeに絞って、処理を記述する、ということができます。エラー処理のために必要なデータを例外クラスに持たせたい場合に初めて、MyExceptionを拡張するなどして、別に新たな例外クラスを作る、という方針をとるのが良いでしょう。