- PR -

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

投票結果総投票数:19
親も close すべき 13 68.42%
親は放っておくべき 6 31.58%
  • 投票は恣意的に行われます。統計的な調査と異なり、投票データの正確性や標本の代表性は保証されません。
  • 投票結果の正当性や公平性について、@ITは一切保証も関与もいたしません。
投稿者投稿内容
squeak
会議室デビュー日: 2005/05/31
投稿数: 6
投稿日時: 2007-01-15 07:05
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6378948
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6266377

1.6の最新版では
直されているっぽいです
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2007-01-15 22:09
引用:

squeakさんの書き込み (2007-01-15 07:05) より:
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6378948
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6266377

1.6の最新版では
直されているっぽいです


ありがとうございます。バグなのでしたか。てっきり仕様だと思っていました。そのため、バージョン間の比較もしていませんでした。

しかし、BufferedWriter は JDK 1.1 から備わったクラスですが、JDK 1.1 が登場したのは、調べると 1997年2月、とあります。10年近くずっと直されなかったということなのでしょうか。怖すぎます。こんな基本的なクラスがバグを抱えていたとすれば、バグ予測法を考えると Java はまだまだたくさんバグを抱えているということが推測できます。

また、これをバグなのか仕様なのかを判断する基準がなんなのかが分かりません。そもそも(Sun がおこなっているであろうと思われる)ユニットテストでひっかからないというのもおかしな話です。

--
unibon {B73D0144-CD2A-11DA-8E06-0050DA15BC86}
kuma
大ベテラン
会議室デビュー日: 2004/02/25
投稿数: 110
投稿日時: 2007-01-16 00:24
bw.close()でなぜbw=nullと補完したいのでしょうか?
コード:
	    } finally {
	        out.close();
	        out = null;
	        cb = null;
	    }


ここのout.close();
でIOExceptionがでれば
そのまま何度os.close()を行っても成立するはずがありません。
前にも書いたとおり、どのクラスがラップされているか
BufferedWriterを使用する側では完全に解るとはいいきれません。
お犬様
ベテラン
会議室デビュー日: 2003/01/26
投稿数: 67
投稿日時: 2007-01-16 00:31
確かに BufferedWriter はおおもとの Writer を close() するように変更されたらしいのですが、OutputStreamWriter は依然として おおもとの OutputStream を close() しないようです。なので unibonさんが仰っていたディスクフルの場合などを考えると、やはり、自力で FileOutputStream を close() した方が良いようです。以下、検証用のサンプルです。
コード:
final boolean[] closed = { false };
try{
    BufferedWriter w = new BufferedWriter(new OutputStreamWriter(new OutputStream(){
        public void write(int b) throws IOException { throw new IOException(); }
        public void close(){ closed[0] = true; }
    }));
    w.write("test");
    w.close();
}finally{
    System.out.println( "closed : " + closed[0] );
}

こんなんで Closed, fixed にしちゃうような人が「バグだと認めた」とか言われても……

引用:
また、これをバグなのか仕様なのかを判断する基準がなんなのかが分かりません。そもそも(Sun がおこなっているであろうと思われる)ユニットテストでひっかからないというのもおかしな話です。

これは私の認識ですが、そもそも close() で例外が発生した場合に どのようにすべきかという仕様自体が無いのではないかと思います。故にテストも存在しないのではないかと…… 本当のところはどうなのかわかりませんけどね。
kuma
大ベテラン
会議室デビュー日: 2004/02/25
投稿数: 110
投稿日時: 2007-01-16 00:48
引用:

Tdnr_Symさんの書き込み (2007-01-15 00:12) より:
こんばんは。

BufferedWriterのcloseに限らず、Javaにも限らず、
一般的なプログラミングの作法として…

ファイルやDB接続などのリソースのクローズに失敗してしまったら、
あとは素直に「後始末をあきらめる」だけのような気がしますが?

少なくとも私は、「後始末エラー」や「ログ出力エラー」の
異常系は記述しません。「どうしようもないね」とあきらめてます。

# 私、間違ってます??



私はTdnr_Symさんの言っているとおり
ファイルやDB接続などのリソースのクローズに失敗してしまったら、
あとは素直に「後始末をあきらめる」だけ
だと思っています。
ディスクフルの場合
などは予めflushしてやることでExceptionが発生することが解るのですから
(今回の変更はラップしているクラスを使いまわしている時のしのぎに過ぎないと思っています)
大抵の場合Writerを構築している中でcloseまで処理していることが多いのですが
そうでない場合、どうその構成を知ろうとしているのか疑問でなりません。
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2007-01-16 01:12
引用:

kumaさんの書き込み (2007-01-16 00:48) より:
私はTdnr_Symさんの言っているとおり
ファイルやDB接続などのリソースのクローズに失敗してしまったら、
あとは素直に「後始末をあきらめる」だけ
だと思っています。



(1から数えて)14番目の投稿( http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=36027&forum=12&start=13 )でも書きましたが、今回の場合は、クローズに失敗するのは BufferedWriter のクローズであって、おおもとの(underlying な) FileOutputStream はクローズされないままになっているのです。オープンしたものがクローズされないままになっているわけですし、そもそもそのオブジェクトに対してクローズが試行すらされていないわけですから、諦めがつきません。

私も、FileOutputStream のクローズを試してそれに失敗したのなら、あとは素直に諦めるだけです。

--
unibon {B73D0144-CD2A-11DA-8E06-0050DA15BC86}
kuma
大ベテラン
会議室デビュー日: 2004/02/25
投稿数: 110
投稿日時: 2007-01-16 22:10
なぜ
BufferedWriterのflushが呼べないのでしょう?
また
IOExceptionをcatchした後なぜ
bw.close()
が呼べないのでしょう?
CharsetEncoderを継承したクラスがしっかりとresetしていれば
ディスクフルの場合でもbw.close()は
FileOutputStreamのcloseを呼び出していると読めるのですが
私が見ているソースが違うのだろうか・・
お犬様
ベテラン
会議室デビュー日: 2003/01/26
投稿数: 67
投稿日時: 2007-01-16 23:19
ちょっと古い部分にレスをつけます。

>> unibonさん
引用:
そもそも close の中で close と flush の両方をしている(順序はもちろん flush の後に close)、というようにメソッドが複合的な処理をしていることがまずいのではないでしょうか。


C言語の標準関数 fclose() も内部で fflush() 呼びますし、私はこれは一般的な動作だと思っています。

引用:
無理に隠蔽せずに、close の前に flush は必ず呼ばないといけない、そして flush を呼ばずに close を呼んだら (RuntimeException である) IllegalStateException あたりを throw してしまえば良いと思います。
こうすることで close と flush の役割が明確になります

私は現状でも flush() と close() の役割は明確だと思っています。
・flush() した後は write() できる。
・close() した後は write() できない。
この考え方でいけば、例えば base64 エンコードをする OutputStream を考えた時、最後にエンコードしたデータが 3バイトに満たなかった場合、その分だけ = 記号を最後に付加するような処理は close() で実装すると思いますし、flush() では 3バイト未満のデータはバッファに残ったままになると思います。



>>kumaさん
引用:
BufferedWriterのflushが呼べないのでしょう?

今回 unibonさんが仰っていたケースでは出力先が OutputStreamWriter や FileOutputStream に固定されているため、その方法で上手く行くかもしれません(※)。しかしながら、前述したような実装の base64 をエンコードする OutputStream に出力していた場合、flush() と close() の書き出すデータが同一でないため、close() した時だけディスクフルの例外が発生するという事態もあり得ますよね。つまり一般的に考えた場合 close() の前に必ず flush() を呼んだとしても、close() の中でデータを出力しないという保証にはならないだろうと私は思います。

引用:
IOExceptionをcatchした後なぜ
bw.close()
が呼べないのでしょう?

bw.close() で IOException が発生していた場合、何のリアクションも取らずに再び bw.close() を実行しても再び同じ原因で IOException が発生する可能性が高いと思われます。

引用:
CharsetEncoderを継承したクラスがしっかりとresetしていれば

CharsetEncoder は OutputStream や InputStream のような構造になっていないです。CharsetEncoder は encode(CharBuffer, ByteBuffer, boolean) や flush(ByteBuffer) の引数で与えられる ByteBuffer に変換後のデータを出力します。 CharsetEncoder の reset() は、例えば ISO-2022-JP の実装クラスにおいて内部で GL の状態を持っていたときに、その内部の GL の状態を初期状態に戻すためのものです。

※ちゃんと OutputStreamWriter の実装を見てないので間違っているかもしれませんが、例えば ISO-2022-JP にエンコードしていた場合は close() だけで GL に ASCII を呼び出すエスケープシーケンスが出力される可能性もあるため、close() の直前に flush() を呼んだとしても、close() 内でデータが出力されるかもしれません……

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