Go to Previous Page Go to Contents Go to Java Page Go to Next Page
New Features of Java2 SDK, Standard Edition, v1.4
 
 

Chained Exception Facility

 
 

例外の付け替え

 
 

例外はいろいろなところで役にたっていると思います.デバッグのときにはもちろんですが、アプリケーションを使っているときにも例外に出くわすと思います.(本当は出くわしたくないとは思いますが...)

ところで、例外を使っていると、例外を付け替えたいと思うことがないでしょうか。たとえば、よく使われるのは RMI で使われる RemoteException や、Servlet での ServletException があります。

RMI ではサーバでの例外をクライアントに伝えるには RemoteException しか使用することはできません。RMI でサーバのインタフェースを書くときに、すべてのメソッドに throws RemoteException と記述しますが、この RemoteException を利用してサーバ側での例外をクライアントに伝えるわけです.

そうすると、こんなコードを書くことになります。

    try { 
        foo()  // このメソッドでは XXException が発生します
    } catch (XXException ex) { 
        throw new RemoteException();   // RemoteException への付け替え
    }

クライアント側では、この RemoteException を catch します。例外を catch した時の常套句として printStackTrace メソッドがあります。例外がどこの行で発生したかを示すためのメソッドです.

RemoteException を catch して、printStackTrace メソッドを使用すると....

java.rmi.RemoteException:
        at SampleClient.bar(SampleClient.java:52)

のように表示されます (行数などは適当に書いてます).しかし、ちょっと待ってください.これだと RemoteException が発生しているのは分かりますが、サーバのどこで RemoteException が発生したのか全然分からなくなってしまっています.

ようするに、サーバ側でのもともとの例外の情報 (ここでは XXException の情報) がすっぽり抜け落ちてしまっているのです.これじゃ、デバッグするにも大変です。

そこで、RemoteException や ServletException では例外を new するときに、原因となった例外をコンストラクタの引数に与えることができます。

    try { 
        foo()  // このメソッドでは XXException が発生します
    } catch (XXException ex) { 
        throw new RemoteException("サーバでの例外", ex);
    }

こうすることで、クライアントでもサーバ側のどこで例外が発生したかを知ることができます。

java.rmi.RemoteException: サーバでの例外; nested exception is:
        XXException
        at SampleClient.bar(SampleClient.java:52)
Caused by: XXException
        at SampleServer.bar(SampelServer.java:31)
        at SampleServer.foo(SampelServer.java:15)

Caused by 以下の部分がサーバ側でのスタックトレースになります。

さて、RemtoeException や ServletException ではネストした例外を扱えることが分かりましたが、普通の例外でもこんなことをやってみたくないですが。

たとえば、あるアプリケーションで SomeApliIOException という例外を定義したとします.そして、アプリケーション中で発生した IOException などはそのまま使用するのではなく、この SomeApliIOException に付け替えて投げるようにするなんてことです.

しかし、Java 2 SE, v1.4 までは、RemoteException などの特殊な例外以外は、例外の情報を引き継ぐことができませんでした。

この機能は地味ではあるのですが、結構使い出がありそうです.それでは以降の章で具体的な使い方について見ていきましょう。

 

 
  自前の例外に原因をつける  
 

前振りは長いのですが、実際の使い方は簡単です.今回のサンプルは例外を起こすために書いてあるので、全然意味はないのですが、とりあえずコードがないと分かりにくいので。

アプリケーションのソース ExceptionTest1.java
TestException.java

TestException が例外クラスになります。

使い方は RemoteException の時と同じように、コンストラクタでネストする例外を指定します.ExceptionTest1 クラスで TestException を発生させている部分を次に示します.

 1:    private void test() throws TestException {
 2:        try {
 3:            throw new InterruptedException();
 4:        } catch (InterruptedException ex) {
 5:            throw new TestException(ex);
 6:        }
 7:    }

InterruptedException を使ったのは特に意味はありません。プログラムとしても、3 行目で例外を throw して、4 行目で catch するのはまったく意味がないのですが、サンプルなのでゆるしてください.

見ていただきたいのは、5 行目です。コンストラクタの引数に原因となる例外を入れています.これを実行すると次のようになります。

C:\temp>java ExceptionTest1
TestException: java.lang.InterruptedException
        at ExceptionTest1.test(ExceptionTest1.java:14)
        at ExceptionTest1.<init>(ExceptionTest1.java:4)
        at ExceptionTest1.main(ExceptionTest1.java:19)
Caused by: java.lang.InterruptedException
        at ExceptionTest1.test(ExceptionTest1.java:12)
        ... 2 more

ちゃんと TestException の原因となる InterruptedException のスタックトレースが (省略されてはいますが) 表示されています.

さて、例外はどのように記述するのでしょうか。今までは、例外を派生するときには 2 つのコンストラクタを記述するだけでした (1 つはデフォルトコンストラクタなので、実質的には 1 つですが)。

新しい例外ではコンストラクタが 4 つになったので、これを記述するだけです.コンストラクタの中身も super を呼び出すだけです。

public class TestException extends Exception {
    public TestException(String message){
        super(message);
    }
 
    public TestException(String message, Throwable cause){
        super(message, cause);
    }
 
    public TestException(Throwable cause){
        super(cause);
    }
}

注意しなければいけないのは、原因となる例外は Exception クラスではなく、Throwable クラスであるということです。Exception クラスは Throwable クラスの派生クラスなのですが、この他に Throwable の派生クラスには Error クラスがあります。ネストさせるときには、どちらの障害も原因に指定できるようにするわけです.実際には Error をキャッチすることはないと思いますけど...

 

 
 

すでに用意されている例外に原因を付加する

 
  自分で例外クラスを作った場合はいいのですが、もともとある IOException などの例外をネストさせるにはどうすればいいでしょうか。

とりあえず、同じように記述してみましょう。

 1:    private void test() throws IOException {
 2:        try {
 3:            throw new InterruptedException();
 4:        } catch (InterruptedException ex) {
 5:            throw new IOException(ex);
 6:        }
 7:    }

しかし、これではコンパイルがとおりません。

ExceptionTest.java:17: シンボルを解釈処理できません。
シンボル: コンストラクタ IOException  (java.lang.InterruptedException)
位置    : java.io.IOException の クラス
            throw new IOException(ex);
                  ^
エラー 1 個

IOException の JavaDoc を見てみると、確かにこんなコンストラクタはありません。さて、どうしましょう。

Exception の親クラスの Throwable の JavaDoc を見てみたら、ありました、こんなときに使えるメソッドが。initCause メソッドがそのメソッドです。

このメソッドはコンストラクタでネストする例外をしないときにしか使用できません。また、initCause をコールできるのは 1 度だけです.

アプリケーションのソース ExceptionTest2.java

例外を生成している部分を以下に示します.

 1:    private void test() throws IOException {
 2:        try {
 3:            throw new InterruptedException();
 4:        } catch (InterruptedException ex) {
 5:            IOException exception = new IOException();
 6:            exception.initCause(ex);
 7:            throw exception;
 8:        }
 9:    }

5 行目で生成した例外に、6 行目の initCause で原因となる例外を指定しています.後は throw するだけです.

 

 
 

原因を探る

 
 

例外の原因となった例外も含めたスタックトレースは表示できました。しかし、このスタックとレースの表示は原因の例外のスタックトレースの部分が省略されてしまっています。そこで、作ってみたのが次のサンプルです。

アプリケーションのソース ExceptionTest3.java

このサンプルでは原因を取り出して、その原因のスタックトレースを表示させています。

 1:    public ExceptionTest3(){
 2:        try {
 3:            test();
 4:        } catch (TestException ex) {
 5:            Throwable th = ex.getCause();
 6:            th.printStackTrace();
 7:        }
 8:    }

原因の例外を取得するには getCause メソッドを使用します。取得できたら、普通の例外と同じように扱うことができます。原因がないときには getCause メソッドの戻り値は null になります。

C:\temp>java ExceptionTest3
java.lang.InterruptedException
        at ExceptionTest3.test(ExceptionTest3.java:13)
        at ExceptionTest3.<init>(ExceptionTest3.java:4)
        at ExceptionTest3.main(ExceptionTest3.java:20)

 

 
 

スタックトレースを使ってみる

 
 

例外のスタックトレースはデバッグにはなくてはならない情報です。しかし、今まではスタックトレースは表示するだけで、プログラム中で使用することができませんでした。

J2SE, v1.4 ではスタックトレースを使用するために StackTraceElement というクラスが新たに追加されました。これを使用したサンプルが ExceptionTest4 です。

アプリケーションのソース ExceptionTest4.java

 

 1:    public ExceptionTest4(){
 2:        try {
 3:            test();
 4:        } catch (TestException ex) {
 5:            StackTraceElement[] element = ex.getStackTrace();
 6:            
 7:            for (int i = 0 ; i < element.length ; i++) {
 8:                System.out.println(element[i].toString());
 9:            }
10:        }
11:    }

スタックトレースを取得するには Throwable クラスの getStackTrace メソッドを使用します。getStackTrace メソッドの戻り値は StackTraceElement オブジェクトの配列です。

このサンプルでは取得できたスタックトレースを単に出力しているだけです。実行結果は次のようになります。

C:\temp>java ExceptionTest4
ExceptionTest4.test(ExceptionTest4.java:18)
ExceptionTest4.<init>(ExceptionTest4.java:4)
ExceptionTest4.main(ExceptionTest4.java:23)

このスタックトレースには原因となる例外のスタックトレースは含まれません。原因の例外のスタックトレースは、先ほどの getCause メソッドを使用して、原因例外を取得し、それに対し getStackTrace メソッドを行うことで取得できます。

StackTraceElemnt クラスには、スタックトレースのクラス名や行数などを取得するための getter メソッドが用意されています。それらを使用すれば、printStackTrace メソッドを使用したのと同じような表示を行うことも可能です。

アプリケーションのソース ExceptionTest5.java

ExceptionTest5 で使用したのは getClassName, getMethodName, getFileName, getLineNumber メソッドです。それぞれ、クラス名、メソッド名、ファイル名、例外の発生した行数を戻します。

    public ExceptionTest5(){
        try {
            test();
        } catch (Exception ex) {
            StackTraceElement[] element = ex.getStackTrace();
            
            System.out.println(ex.getClass().getName());
            for (int i = 0 ; i < element.length ; i++) {
                System.out.print("    at ");
                System.out.print(element[i].getClassName());
                System.out.print("." + element[i].getMethodName());
                System.out.print("("+ element[i].getFileName());
                System.out.print(":" + element[i].getLineNumber());
                System.out.println(")");
            }
        }
    }

さて、さっそく実行してみましょう。

C:\temp>java ExceptionTest5
TestException
    at ExceptionTest5.test(ExceptionTest5.java:24)
    at ExceptionTest5.<init>(ExceptionTest5.java:4)
    at ExceptionTest5.main(ExceptionTest5.java:29)

printStackTrace を使用したときと、ほとんど同じように出力することができました。やっていないのはメッセージや原因例外があった場合についてです。この 2 つは例外クラスから情報をとるようにすれば可能です。

 

 
 

最後に

 
 

かなり地味な機能ですが、こういう気のきいた機能が生産性の向上に非常に役立ったりします。少なくとも、アプリケーションに独自の例外クラスを作るのであれば、この機能を使用してインプリするようにしましょう。これだけで、デバッグがとても楽になると思いますよ。

今回使用したサンプルはここからダウンロードできます。

参考 URL

(Feb. 2002)

 

 
 
Go to Previous Page Go to Contents Go to Java Page Go to Next Page