为什么在发出终端操作后没有Java close()流?

Séb*_*ois 21 java java-8 java-stream

在阅读了https://www.airpair.com/java/posts/spring-streams-memory-efficiency之后,我很想从数据库中传出结果,但正如我与同事讨论的那样(cfr.评论他补充说文章),需要记住使用try-with-resources构造来避免任何内存泄漏.

  1. 为什么Java 8库不会在每次终端操作后自行关闭流(不必在try-with-resources中包装流实例化)?
  2. 如果适用,是否有任何将此功能添加到Java的计划,或者请求它是否有意义?

Bri*_*etz 27

因为需要显式资源释放的流实际上是一个非常不寻常的情况.因此,我们选择不对所有流执行负担,这些内容仅对.01%的使用有价值.

我们制作了Stream Autocloseable,以便您可以根据需要从源中释放资源,但这是我们停止的地方,并且有充分的理由.

这样做不仅会使大多数用户自动负担他们不需要的额外工作,而且这也违反了一般原则:分配资源的人负责关闭资源.你打电话的时候

BufferedReader reader = ...
reader.lines().op().op()...
Run Code Online (Sandbox Code Playgroud)

是打开资源的人,而不是流库,应该关闭它.实际上,由于关闭在某个资源保持对象上调用访问器方法而产生的流有时会关闭底层对象,因此您可能不希望流BufferedReader为您关闭- 您可能希望它在调用后保持打开状态.

如果要关闭资源,这也很容易:

try (BufferedReader reader = ...) {
    reader.lines().op()...
}
Run Code Online (Sandbox Code Playgroud)

你可能正在以特定的方式使用流,所以它可能看起来"显而易见"流应该做什么 - 但是有比你更多的用例.因此,我们不是满足于特定用例,而是从一般原则接近它:如果您打开流,并且希望它关闭,请自行关闭它,但如果您没有打开它,则不能让您关闭.

  • 此外,对于终端操作`iterator`和`spliterator`,我们无法在那时关闭流,因为如果我们这样做,结果迭代器/分裂器将无法工作.可以肯定的是,我们可以采取的替代政策也可能没有"错误",但我们选择的政策是基于明确和明智的原则. (6认同)
  • @BrianGoetz 我只是花了一整天的时间来找出像 `boolean res = Files.list(dir).anyMatch(e -> true);` 这样的简单行留下未关闭的资源。请注意,实际上不是我打开资源,它是在 Files 类中打开的,它的行为在那里被描述为`.onClose(()->ds.close());`。对于您的示例,`bufferedReader.lines().op().op()...` 在lines() 方法中没有添加流关闭处理程序,因此即使流最终关闭,用户仍然需要处理bufferedReader 本人。 (4认同)
  • 更正:引入流以提供一种更抽象的方式来表达对任意数据集的聚合操作; 这是实际并行性的必要前提条件,但我认为每个人都会同意Stream即使没有并行性也非常有用.对"close()"和"Autocloseable"的支持表示扩展设计,以便为源代表用户管理资源的流提供最小的支持(而不是由VM管理的内存).我认为这真的是"2%空"而不是"98%满",你现在可能只是在2%游泳. (3认同)
  • @BrianGoetz terminalOperator 应该是真正的终端。如果流不再可用,让流打开有什么关系? (3认同)
  • 我现在更好地理解了为什么 `Stream` 目前是这样的,所以我会接受这个答案。但是对于我作为最终用户和我目前的理解,Java 库中仍然需要一个(另一个?)流实现来负责始终关闭 finally 块中的底层资源。 (2认同)
  • "自动执行此操作会给大多数用户带来他们不需要的额外工作",这是我不明白的:用户负担的工作是什么?自动调用"关闭"?对于大多数流而言,无论如何都是无操作,我不明白这个负担. (2认同)
  • 这个决定非常令人困惑,因为像“Files.list()”这样的方法直接返回流,并且旨在以流畅的方式使用,而无需将中间流分配给变量。您必须将其包装到 try-with-resources 中,这是完全不直观的。`Stream.onClose` 的文档应该提到,除非有人显式保存流并关闭它,否则它们永远不会运行。 (2认同)