今suzukiとiwamotoが理解している範囲で説明します。
ただあいまいにしか理解できていないところもありますので、指摘をよろしくおねがいします。

とりあえず、例外を処理する責務のないメソッドを考えることとします。
お題を

  1. リソースは必ず解放させる。
  2. 例外は必ず外に投げる。

とします。

一般的にはtry-catch-finallyでリソースの解放をするようです。

public void sub() throws IOException {
    FileReader tFileReader = new FileReader("File.txt");
    BufferedReader tBufferedReader = null;
    try {
           tBufferedReader = new BufferedReader(tFileReader);
           while (tBufferedReader.readLine() != null) {}
    } catch (IOException aCause) {
           throw aCause;
    } finally {
           if (tBufferedReader != null) {
               tBufferedReader.close();
           }
           tFileReader.close();
    }
}

この場合、

  1. 例外が一度も発生しない場合
    リソースの解放が適切に行われます。
  2. try文の中で例外が発生し、close時に例外は発生しない場合。
    外に適切に例外の内容が伝わり、リソースの解放も正常に行われます。
  3. try文の中で例外が発生せずにclose時に例外が発生する場合。
    リソースの解放はできていませんが、その例外が外に伝わります。
  4. try文の中で例外が発生し、close時にも例外が発生した場合。
    try文での例外がcloseの例外に握りつぶされてしまいます。

 連鎖反応でclose失敗していた場合などを考えるとtry文の中で起こった例外が知りたいはずです。

yamaguchiさんに教わったコードでは、

public void main() throws IOException {
 FileReader tFileReader = new FileReader("File.txt");
 try {
     BufferedReader tBufferedReader = new BufferedReader(tFileReader);
     try {
  while (tBufferedReader.readLine() != null) {
  }
     } catch (IOException aCause) {
  closeSilently(tBufferedReader);
  throw aCause;
     } catch (RuntimeException aCause) {
  closeSilently(tBufferedReader);
  throw aCause;
     } catch (Error aCause) {
  closeSilently(tBufferedReader);
  throw aCause;
     }
     tBufferedReader.close();
 } catch (IOException aCause) {
     closeSilently(tFileReader);
     throw aCause;
 } catch (RuntimeException aCause) {
     closeSilently(tFileReader);
     throw aCause;
 } catch (Error aCause) {
     closeSilently(tFileReader);
     throw aCause;
 }
 tFileReader.close();
}
private void closeSilently(Closeable aCloseable) {
 try {
     aCloseable.close();
 } catch (Throwable aCause) {
 }
}

この場合は、

  1. 例外が一度も発生しない場合
    リソースの解放が適切に行われます。
  2. try文の中で例外が発生し、close時に例外は発生しない場合。
    外に適切に例外の内容が伝わり、リソースの解放も正常に行われます。
  3. try文の中で例外が発生せずにclose時に例外が発生する場合。
    リソースの解放はできていませんが、その例外が外に伝わります。
  4. try文の中で例外が発生し、close時にも例外が発生した場合。
    try文での例外がcloseの例外に握りつぶされずに外に伝わっていきます。 

両者を比較して4の部分が改善されていることがわかると思います。

また、

  1. Compositeにおいてあるクラスがほかのクラスのリーソースの解放の責務も担っている場合
  2. Compositeにおいてあるクラスがほかのクラスのリーソースの解放の責務を担っていない場合

でコードスタイルが変わってきます。
上の例が、2.のときに推奨されるやり方です。(java.ioは1.のケースですが2.のケースでも記述できるのでそうしています)

1.のケースのコードスタイル

public void main2()throws IOException {
 Reader tReader = new FileReader("File.txt");
 try{
     BufferedReader tBufferedReader = new BufferedReader(tReader);
     tReader = tBufferedReader; 
     while(tBufferedReader.readLine() != null){}
 }catch(IOException aCause){
     closeSilently(tReader);
     throw aCause;
 }catch(RuntimeException aCause){
     closeSilently(tReader);
     throw aCause;
 }catch(Error aCause){
     closeSilently(tReader);
     throw aCause;
 }
 tReader.close();
}

ラップしているBufferedReaderがリソース解放されるとFileReaderのリソースも解放してくれるというわけです。

カテゴリー: 技術情報

2件のコメント

koreyasu · 2007-06-22 23:03

箇条書きよりもテーブルの方が分かりやすいかも。
プログラムのインデントが変なところが…後で直しておきます。

ojarz · 2007-06-23 08:56

うむ。例外処理は、相変わらずメンドクサソウだな。

現在コメントは受け付けていません。