Since: May./04th/2005
例外クラスのインスタンス化で挙げたとおり、例外を新規生成すると、スタックトレースは生成された場所でのものになります。詳細メッセージも、明示的に指定しない限り含まれません。
これではデバッグが難しいので、Java2 1.4以上では、捕捉した情報を、新たにnewするオブジェクトにセットできるようになっています。この機構を、例外チェーンと呼びます。例外チェインの実装には、全ての例外オブジェクトのスーパークラスである java.lang.Throwable に追加されたメソッド initCause() か、コンストラクタ Throwable(Throwable cause)/Throwable(String message, Throwable cause) を使います。
次のリストは実行時引数 args[0] をプリントするだけのものです。実行時引数が与えられないと、非チェック例外 java.lang.ArrayIndexOutOfBoundsException が発生します。発生した例外を try-catch 構文で捕捉していますが、新規に例外オブジェクト newEx を生成しています。新規に生成されたばかりのオブジェクト newEx には、実際に発生した例外情報は含まれていません。ここでは、キャッチした元の例外を、メソッド Throwable#initCause() でセットしています。
出力結果を見ると、一度目の printStackTrace() では、当該例外オブジェクトが生成された場所の情報しか含まれていないのに対して、initCause() 後の printStackTrace() では、キャッチされた例外情報が "Caused by:" 以下に追加されていることが分かります。
ここでは Throwable 型オブジェクトを、initCause() でセットしましたが、例外オブジェクトのコンストラクタ引数に指定しても同様です。こうしてセットされた原因例外オブジェクトは、メソッド getCause() でも取得可能です。
class ExceptionChainDemo {
public static void main(String[] args) {
try {
System.out.println(args[0]);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("例外の新規作成");
Exception newEx = new Exception();
newEx.printStackTrace();
System.out.println("例外に原因追加");
newEx.initCause(e);
newEx.printStackTrace();
System.out.println("原因の抽出");
(newEx.getCause()).printStackTrace();
}
}
}
>javac ExceptionChainDemo.java
>java ExceptionChainDemo
例外の新規作成
java.lang.Exception
at ExceptionChainDemo.main(ExceptionChainDemo.java:7)
例外に原因追加
java.lang.Exception
at ExceptionChainDemo.main(ExceptionChainDemo.java:7)
Caused by: java.lang.ArrayIndexOutOfBoundsException: 0
at ExceptionChainDemo.main(ExceptionChainDemo.java:4)
原因の抽出
java.lang.ArrayIndexOutOfBoundsException: 0
at ExceptionChainDemo.main(ExceptionChainDemo.java:4)
initCause() を使わずに、コンストラクタ引数で原因例外を与えることもできます。
SDK 1.3 以下の場合は、例外クラスのコンストラクタは、文字列しか引数に取れないので、次のように実装していました。
try {
// チェック例外IOExceptionが発生するかもしれない処理
FileReader obj = new FileReader("filename.txt");}
} catch (IOException e) {
// IOExceptionを捕捉して非チェック例外RuntimeExceptionでラップしてスロー
throw new RuntimeException(e.toString());
}
SDK 1.4 以上では、コンストラクタで原因例外を指定するには、Throwable(Throwable cause) 型のコンストラクタを使うことができます。
try {
// チェック例外IOExceptionが発生するかもしれない処理
FileReader obj = new FileReader("filename.txt");}
} catch (IOException e) {
// IOExceptionを捕捉して非チェック例外RuntimeExceptionでラップしてスロー
throw new RuntimeException(e);
}
自分で例外クラスを作成した場合は、super(Throwable cause) などを指定する必要があります。次の場合は、RuntimeException を継承して MyException を作っています。コンストラクタ MyException(Throwable cause) の中では、super(cause) によって、RuntimeException に実装されたコンストラクタ RuntimeException(Throwable cause) を明示的に呼び出しています。
class MyException extends RuntimeException {
MyException(Throwable cause) {
super(cause);
}
}
super(cause) は、 this.initCause(cause) と書き換えても同様です。
次の例は、コンストラクタでラッピングを実現したものです。getResource() がスローする FileNotFoundException をキャッチして、ResourceNotFoundException でラッピングしてスローしなおしています。
この例で定義しているのは、次の三つのクラスです。
ResourcesManagergetResource() は、getFileResource() を呼び出します。getFileResource() では、チェック例外 java.io.FileNotFoundException をスローしています。キャッチされた例外を、 ResourceNotFoundException 型でラップしています。ResourceNotFoundExceptionRuntimeExcepotion を継承し、コンストラクタとして、引数に Throwable 型を受け取るものを定義しています。
ExceptionWrappingDemoResourceManager をインスタンス化して、メソッド getResource() を呼び出しています。
getResource() 実行時に FileNotFoundException が発生するので、そのままであれば、getResource() のメソッド宣言に throws リストを定義して、呼び出し元で try-catch する必要があります。しかし、ここでは、当該メソッド内の catch 節内において、非チェック例外でラッピングしているため、その必要はありません。
また、getResource() メソッドの実装を変更して、データベース使用時の例外である SQLException や、ネットワーク使用時の例外である SocketException などがスローされるようになっても、呼び出し元が受け取るのは ResourceNotFoundException 型オブジェクトのままでよく、元々の例外情報はメソッド getCause() で取得することができます。
import java.io.FileNotFoundException;
class ResourcesManager {
void getResource() {
try {
getFileResource();
} catch (FileNotFoundException e) {
// 例外のラッピング
ResourceNotFoundException ex = new ResourceNotFoundException(e);
throw ex;
}
}
void getFileResource() throws FileNotFoundException {
throw new FileNotFoundException();
}
}
class ResourceNotFoundException extends RuntimeException {
ResourceNotFoundException(Throwable cause) {
super(cause);
// 又は、this.initCause(cause);
}
}
class ExceptionWrappingDemo {
public static void main(String[ ] args) {
ResourcesManager obj = new ResourcesManager();
obj.getResource();
}
}
C:\java>javac ExceptionWrappingDemo.java
C:\java>java ExceptionWrappingDemo
Exception in thread "main" ResourceNotFoundException
at ResourcesManager.getResource(ExceptionWrappingDemo.java:9)
at ExceptionWrappingDemo.main(ExceptionWrappingDemo.java:29)
Caused by: java.io.FileNotFoundException
at ResourcesManager.getFileResource(ExceptionWrappingDemo.java:15)
at ResourcesManager.getResource(ExceptionWrappingDemo.java:6)
... 1 more
"Caused by:" の最終行の "... 1 more" は、直前のスタックトレースの最終行を表します。よって、展開すると次のようになります。
Exception in thread "main" ResourceNotFoundException
at ResourcesManager.getResource(ExceptionWrappingDemo.java:9)
at ExceptionWrappingDemo.main(ExceptionWrappingDemo.java:29)
Caused by: java.io.FileNotFoundException
at ResourcesManager.getFileResource(ExceptionWrappingDemo.java:15)
at ResourcesManager.getResource(ExceptionWrappingDemo.java:6)
at ExceptionWrappingDemo.main(ExceptionWrappingDemo.java:29)
"Caused by:" のスタックトレースを下から順番に読むと、 29 行目の "obj.getResource();" から、6 行目の "getFileResource();" に至り、更に 15 行目の "throw new FileNotFoundException();" で例外 java.io.FileNotFoundException が発生したことが分かります。