web-dev-qa-db-ja.com

Files.lines(および同様のStreams)が自動的に閉じられないのはなぜですか?

Streamのjavadocの状態:

ストリームにはBaseStream.close()メソッドがあり、AutoCloseableを実装していますが、ほとんどすべてのストリームインスタンスを実際に使用後に閉じる必要はありません。一般に、ソースがIOチャネル(Files.lines(Path、Charset)によって返されるものなど))であるストリームのみを閉じる必要があります。ほとんどのストリームは、コレクション、配列、または生成関数によってサポートされます。 、特別なリソース管理を必要としません(ストリームを閉じる必要がある場合、try-with-resourcesステートメントでリソースとして宣言できます)。

したがって、ほとんどの場合、collection.stream().forEach(System.out::println);のようなワンライナーでStreamsを使用できますが、Files.linesおよびその他のリソースに裏打ちされたストリームでは、try-with-resourcesステートメントを使用する必要がありますまたは、リソースをリークします。

これはエラーが発生しやすく、不必要だと思います。 Streamsは1回しか反復できないため、Files.linesの出力を反復したらすぐに閉じてはならない状況はないようです。したがって、実装は暗黙的にcloseを暗黙的に呼び出す必要があります。端末操作の終了。私は間違っていますか?

47
MikeFHay

はい、これは意図的な決定でした。両方の選択肢を検討しました。

ここでの運用設計の原則は、「リソースを取得した人は誰でもリソースを解放する」ことです。 EOFを読み取ってもファイルは自動的に閉じません。ファイルを開いた人がファイルを明示的に閉じることを期待しています。 IOリソースによって支援されるストリームは同じです。

幸いなことに、言語はこれを自動化するためのメカニズムtry-with-resourcesを提供します。 StreamはAutoCloseableを実装しているため、次のことができます。

try (Stream<String> s = Files.lines(...)) {
    s.forEach(...);
}

「オートクローズすることは本当に便利なので、ワンライナーとして書くことができる」という議論はニースですが、ほとんどは犬を振る尾です。ファイルまたは他のリソースを開いた場合は、それを閉じる準備もする必要があります。効果的で一貫性のあるリソース管理は「これを1行で記述したい」よりも優先されます。

61
Brian Goetz

@BrianGoetzの回答に加えて、より具体的な例があります。 Streamにはiterator()のようなエスケープハッチメソッドがあることを忘れないでください。あなたがこれをしているとします:

_Iterator<String> iterator = Files.lines(path).iterator();
_

その後、hasNext()next()を数回呼び出してから、このイテレータを放棄するだけです:Iteratorインターフェイスはそのような使用を完全にサポートします。 Iteratorを明示的に閉じる方法はありません。ここで閉じることができるオブジェクトはStreamだけです。このように、それは完全にうまく機能します:

_try(Stream<String> stream = Files.lines(path)) {
    Iterator<String> iterator = stream.iterator();
    // use iterator in any way you want and abandon it at any moment
} // file is correctly closed here.
_
15
Tagir Valeev

さらに、「1行の書き込み」が必要な場合。これを行うことができます:

Files.readAllLines(source).stream().forEach(...);

ファイル全体が必要で、ファイルが小さいことが確実な場合に使用できます。遅延読み取りではないからです。

4

私のように怠け者で、「例外が発生した場合、ファイルハンドルが開いたままになる」ことを気にしない場合は、次のようなオートクロージングストリームでストリームをラップできます(他の方法もあります)。

  static Stream<String> allLinesCloseAtEnd(String filename) throws IOException {
    Stream<String> lines = Files.lines(Paths.get(filename));
    Iterator<String> linesIter = lines.iterator();

    Iterator it = new Iterator() {
      @Override
      public boolean hasNext() {
        if (!linesIter.hasNext()) {
          lines.close(); // auto-close when reach end
          return false;
        }
        return true;
      }

      @Override
      public Object next() {
        return linesIter.next();
      }
    };
    return StreamSupport.stream(Spliterators.spliteratorUnknownSize(it, Spliterator.DISTINCT), false);
  }
1
rogerdpack