使用 Stream.sum() 多次消费流

Cag*_*tay 5 java java-stream

流只能被操作(调用中间或终端流操作)一次。

我明白了这个想法,但是为什么找到流的总和不会消耗它呢?我可以运行下面的代码,没有任何异常。

double totalPrice = stream.mapToDouble(product -> product.price).sum();
List<Product> products = stream.map(this::convert).collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)

为什么sum不是终端运营商?它与将流元素收集到列表中有何不同?

Zab*_*uza 4

文档

未指定流是否可以被消费一次或多次。文档说“应该操作......仅一次”,而不是“必须”或“可以”。

取决于流的底层实现和来源。因此,对于流来说,多次执行它的能力通常是未定义的。


实施

您也许能够找到一个可以使用两次或多次的流,特别是对于自定义实现或预防检测实施成本可能太高的实现。

但同样,文档不支持它。它可能是当前的实现,但这可以在任何 Java 版本中随时更改。它可能会抛出异常,可能会导致错误行为,但未指定。

这是一个小例子,它使用sum()抛出异常,因为它有这样的检测(JDK 11):

Stream<Integer> stream = List.of(1, 2, 3, 4).stream();

int first = stream
    .mapToInt(i -> i)
    .sum();

int second = stream
    .mapToInt(i -> i)
    .sum();

// throws IllegalStateException: stream has already been operated upon or closed
Run Code Online (Sandbox Code Playgroud)

结论

正如文档所暗示的,其目的绝对是不要多次使用流。因此,即使某种实现可能可行,也要避免它


为什么?

您可能会问自己为什么Stream不支持多次迭代。毕竟,像ArrayList这样的集合没有任何问题,而且这似乎是一个常见的用例。

流的范围比集合大得多。在典型集合上创建的流可能很容易支持多次迭代。但是与文件或网络连接等资源绑定的流,例如返回的流则Files.lines(...)不能。对于文件来说,这样的功能将占用大量资源且成本高昂,甚至可能不支持其他功能,例如随后可能会关闭的网络连接。

更进一步,您可以轻松创建一个生成随机数的无限流:

Stream<Double> stream = Stream.generate(Math::random);
Run Code Online (Sandbox Code Playgroud)

虽然它可以轻松支持多种用途,但再次迭代时不可能再次生成相同的序列。

另一个例子,一个消耗资源而不恢复它们的流:

Queue<Integer> queue = ...
Stream<Integer> stream = Stream.generate(queue::poll);
Run Code Online (Sandbox Code Playgroud)

该流将从队列中删除元素。使用流后它们就消失了。流的另一次迭代将无法再获取死亡对象。

  • 只是一个小修正,从“ArrayList”创建的流实际上不能多次使用。至少在 JDK11 上是这样。(在从 `ArrayList&lt;Integer&gt;` 创建的同一个 `s` 上尝试两次 `s.mapToInt(i-&gt;i).sum()`。) (3认同)
  • 简而言之,假装他们总是被消耗,以避免奇怪和意想不到的行为。 (2认同)