今suzukiとiwamotoが理解している範囲で説明します。
ただあいまいにしか理解できていないところもありますので、指摘をよろしくおねがいします。
とりあえず、例外を処理する責務のないメソッドを考えることとします。
お題を
- リソースは必ず解放させる。
- 例外は必ず外に投げる。
とします。
一般的には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();
}
}
この場合、
- 例外が一度も発生しない場合
リソースの解放が適切に行われます。 - try文の中で例外が発生し、close時に例外は発生しない場合。
外に適切に例外の内容が伝わり、リソースの解放も正常に行われます。 - try文の中で例外が発生せずにclose時に例外が発生する場合。
リソースの解放はできていませんが、その例外が外に伝わります。 - 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) {
}
}
この場合は、
- 例外が一度も発生しない場合
リソースの解放が適切に行われます。 - try文の中で例外が発生し、close時に例外は発生しない場合。
外に適切に例外の内容が伝わり、リソースの解放も正常に行われます。 - try文の中で例外が発生せずにclose時に例外が発生する場合。
リソースの解放はできていませんが、その例外が外に伝わります。 - try文の中で例外が発生し、close時にも例外が発生した場合。
try文での例外がcloseの例外に握りつぶされずに外に伝わっていきます。
両者を比較して4の部分が改善されていることがわかると思います。
また、
- Compositeにおいてあるクラスがほかのクラスのリーソースの解放の責務も担っている場合
- 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
うむ。例外処理は、相変わらずメンドクサソウだな。
現在コメントは受け付けていません。