- PR -

java.io.BufferedWriter クラスの close メソッドのデザインについての疑問

投票結果総投票数:19
親も close すべき 13 68.42%
親は放っておくべき 6 31.58%
  • 投票は恣意的に行われます。統計的な調査と異なり、投票データの正確性や標本の代表性は保証されません。
  • 投票結果の正当性や公平性について、@ITは一切保証も関与もいたしません。
投稿者投稿内容
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2007-01-14 13:30
件名は「java.io.BufferedWriter クラスの close メソッドのデザインは不完全ではないか?」ぐらいにしたかったのですが、抑えました。

ファイル入出力のコードを書くたびに思うのですが、BufferedWriter の close の仕様がイマイチ納得できません。BufferedWriter は、そのコンストラクターの引数で指定された親(とでも言うのでしょうか?)である Writer の close まで任されているというデザインになっていると思います。しかしなにかの例外(IOException)が発生した時に、それが中途半端になって完全に処理されません。それならば、最初っから close しないほうが良いのではないでしょうか。

たとえば、
コード:
package foo;

import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;

public class Abc {
    
    public static void main(String[] args) throws IOException {
        OutputStream os = new FileOutputStream("c:\\abc.txt");
        Writer w = new OutputStreamWriter(os);
        BufferedWriter bw = new BufferedWriter(w, 0x1000);
        for (int i = 0; i < 10; i++) {
            bw.write("愛 = " + i);
            bw.newLine();
        }
        bw.close();
    }
}


のようなコードの場合、bw.close() がたとえばディスクフルなどで失敗して IOException になった場合、親の w は close されないままになると思います。したがってそのまた親の os も close されないままになり、すなわち OS のファイルシステム上のファイルがオープンされたままの状態になって残ってしまいます。(os の finalize までクローズされない期間が存在する。)
結局、こういう例外まできちんと finally で処理しようとすると、結局は os.close() をするコードをどこかで書いておかないといけないことになります。だったら、最初っから bw.close() は w の close はしない、というようにしてくれたほうがシンプルになって良いと思います。この考えは正しいでしょうか?

(なお、Writer 系と同様に Reader 系でも同様の疑問がありますが、ここでは Writer 系だけに絞ります。Writer 系だと Reader 系にはない flush も考えないといけないので、Writer 系を考えればその中に Reader 系も含んでいることになると思います。)

--
unibon {B73D0144-CD2A-11DA-8E06-0050DA15BC86}
kuma
大ベテラン
会議室デビュー日: 2004/02/25
投稿数: 110
投稿日時: 2007-01-14 15:35
引用:

unibonさんの書き込み (2007-01-14 13:30) より:
コード:

package foo;

import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;

public class Abc {

public static void main(String[] args) throws IOException {
OutputStream os = new FileOutputStream("c:\\abc.txt");
Writer w = new OutputStreamWriter(os);
BufferedWriter bw = new BufferedWriter(w, 0x1000);
for (int i = 0; i < 10; i++) {
bw.write("愛 = " + i);
bw.newLine();
}
bw.close();
}
}


のようなコードの場合、bw.close() がたとえばディスクフルなどで失敗して IOException になった場合、親の w は close されないままになると思います。したがってそのまた親の os も close されないままになり、すなわち OS のファイルシステム上のファイルがオープンされたままの状態になって残ってしまいます。(os の finalize までクローズされない期間が存在する。)
結局、こういう例外まできちんと finally で処理しようとすると、結局は os.close() をするコードをどこかで書いておかないといけないことになります。だったら、最初っから bw.close() は w の close はしない、というようにしてくれたほうがシンプルになって良いと思います。この考えは正しいでしょうか?


--
unibon {B73D0144-CD2A-11DA-8E06-0050DA15BC86}


親は放っておくべき に入れときました。
というのもbw.close()でExceptionが発生するなら全て(bwもwもosも)closeはされてない
(中途半端ではない)からです。
これとは別の観点で
BufferedWriter bw = new BufferedWriter(w, 0x1000);
ここでExceptionが発生した時どうするか?
この時はw.close()してやる必要があると考えています。

#選んだ選択肢を記述とおりに変更

##なんか投票を取り違っていたみたいなので以下追記
##closeメソッド内での選択肢でいうとBufferedWriterのcloseで一緒にcloseする。
##実装者はBufferedWriterに任せるです

[ メッセージ編集済み 編集者: kuma 編集日時 2007-01-14 15:37 ]

[ メッセージ編集済み 編集者: kuma 編集日時 2007-01-14 17:44 ]
お犬様
ベテラン
会議室デビュー日: 2003/01/26
投稿数: 67
投稿日時: 2007-01-14 16:38
私は親も close してくれる方が嬉しいです。正常に処理されている場合について言えば、close の呼び出しの順番を間違える心配がありませんから。また「bw.close() が w の close を『してはいけない』」の理由が弱いので、unibonさんの意見にはあまり賛同できません。

引用:
この考えは正しいでしょうか?

なんと言うか、「正しい」とか「正しくない」というような事柄ではないような気もしますけれども。
Tdnr_Sym
ぬし
会議室デビュー日: 2005/09/13
投稿数: 464
お住まい・勤務地: 明石・神戸
投稿日時: 2007-01-14 17:14
こんにちは。

投票間違えました(~_~;)
親の意味を取り間違えました。

Reader/Writerは、Decoratorパターン(別名:Wrapperパターン)で設計されているのですから、
ラッピングされるコンポーネントクラス(ここでいう親)のWriterのcloseは
最外殻のラッパーであるBufferedWriterに任せるのが、デザインパターン的だと思います。
かつのり
ぬし
会議室デビュー日: 2004/03/18
投稿数: 2015
お住まい・勤務地: 札幌
投稿日時: 2007-01-14 17:30
ソースや詳細は失念したのですが、
前にも他所で似たような議論が行われていて、
今のままで問題ないって結論だったような。

そのときは記憶があいまいですが、Sunの人の意見も引用されていたような・・・

ソース見つけたら、また書き込みます。
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2007-01-14 17:51
投票の選択肢が紛らわしかったみたいですね。すみません。

「親も close すべき」 = 現状のままが良い。
「親は放っておくべき」 = BufferedWriter クラスの close メソッドの中で、ラップしている Writer の close はしないようにする。

です。
現在の仕様との互換性の問題は考えずに、あくまでも、理想的なデザインは、ということでご投票(?)ください。

引用:

お犬様さんの書き込み (2007-01-14 16:38) より:
私は親も close してくれる方が嬉しいです。正常に処理されている場合について言えば、close の呼び出しの順番を間違える心配がありませんから。また「bw.close() が w の close を『してはいけない』」の理由が弱いので、unibonさんの意見にはあまり賛同できません。


正常系だとそうなのかもしれませんが、異常系を気にすると気になるのです。(正常系・異常系と分けることはあまり好きではありませんが。)
また、突っかかるような反論になり恐縮ですが、逆に、積極的に『しなければいけない』という理由もないような気がします。

引用:

Tdnr_Symさんの書き込み (2007-01-14 17:14) より:
Reader/Writerは、Decoratorパターン(別名:Wrapperパターン)で設計されているのですから、
ラッピングされるコンポーネントクラス(ここでいう親)のWriterのcloseは
最外殻のラッパーであるBufferedWriterに任せるのが、デザインパターン的だと思います。


ということは、BufferedWriter クラスの close メソッドが、本来は任せられていることを例外が起きた時にハンドリングし忘れている、ということでしょうか。

引用:

かつのりさんの書き込み (2007-01-14 17:30) より:
ソースや詳細は失念したのですが、
前にも他所で似たような議論が行われていて、
今のままで問題ないって結論だったような。


むか〜しから疑問に思っていたことなので、もしかしたら私が以前に同じようなことを質問したのかもしれません。

--
unibon {B73D0144-CD2A-11DA-8E06-0050DA15BC86}
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2007-01-14 18:26
引用:

引用:

Tdnr_Symさんの書き込み (2007-01-14 17:14) より:
Reader/Writerは、Decoratorパターン(別名:Wrapperパターン)で設計されているのですから、
ラッピングされるコンポーネントクラス(ここでいう親)のWriterのcloseは
最外殻のラッパーであるBufferedWriterに任せるのが、デザインパターン的だと思います。


ということは、BufferedWriter クラスの close メソッドが、本来は任せられていることを例外が起きた時にハンドリングし忘れている、ということでしょうか。


結局は BufferedWriter の close についてというよりも、例外をどうハンドルすべきか、という問題に行き着くように思えてきました。(Java の疑問の多くは例外に行き着いてしまうような。)
すなわち、ラッパーA(ここでは BufferedWriter)が、その中にラップしている(集約している)インスタンスB(ここでは Writer)を持っているわけですが、そのBが例外を投げた場合、Aがtry/catchして処理しないといけないか、ということです。

BufferedWriter の実装を見ると、処理していないです。一方、PrintWriter は try/catch して処理しています。この違いはなんでしょう。また分からなくなりました。

--
unibon {B73D0144-CD2A-11DA-8E06-0050DA15BC86}
kuma
大ベテラン
会議室デビュー日: 2004/02/25
投稿数: 110
投稿日時: 2007-01-14 21:11
引用:

unibonさんの書き込み (2007-01-14 17:51) より:
投票の選択肢が紛らわしかったみたいですね。すみません。

「親も close すべき」 = 現状のままが良い。
「親は放っておくべき」 = BufferedWriter クラスの close メソッドの中で、ラップしている Writer の close はしないようにする。

です。
現在の仕様との互換性の問題は考えずに、あくまでも、理想的なデザインは、ということでご投票(?)ください。

引用:

お犬様さんの書き込み (2007-01-14 16:38) より:
私は親も close してくれる方が嬉しいです。正常に処理されている場合について言えば、close の呼び出しの順番を間違える心配がありませんから。また「bw.close() が w の close を『してはいけない』」の理由が弱いので、unibonさんの意見にはあまり賛同できません。


正常系だとそうなのかもしれませんが、異常系を気にすると気になるのです。(正常系・異常系と分けることはあまり好きではありませんが。)
また、突っかかるような反論になり恐縮ですが、逆に、積極的に『しなければいけない』という理由もないような気がします。

--
unibon {B73D0144-CD2A-11DA-8E06-0050DA15BC86}



次のような時、どのようにしたいとしているのでしょうか?
コード:
class A {
  void createHoge() {
    String fileName = "hoge";
    Writer writer = this.getWriter(fileName);
    writer.close();
  }
  Writer getWriter(String fileName) {
    OutputStream os = new FileOutputStream(fileName);
    Writer w = new OutputStreamWriter(os);
    BufferedWriter bw = new BufferedWriter(w);
    return bw;
  }
}


実際に最外殻のラッパーを使用する側(この場合#createHoge)
がその内部構成について考慮しなければならないのでしょうか?

あとPrintWriterは特殊だと思ったほうがいいです。
(JavaDocにも注釈みたいな形でIOExceptionをスローしないとありますし)
例外を中で処理してしまい、チェックメソッドを通して状態を知るのでは
そのクラスを使用する際、常にチェックを気にしていなければなりません。

スキルアップ/キャリアアップ(JOB@IT)