例外
例外の構文 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を拡張するなどして、別に新たな例外クラスを作る、という方針をとるのが良いでしょう。

