如何在 Java 流上调用多个终端操作

How to call multiple terminal operation on a Java stream

我知道每当我们在 stream 上调用任何 terminal method 时,它都会关闭。

如果我们尝试在关闭的流上调用任何其他终端函数,它将导致 java.lang.IllegalStateException: stream has already been operated upon or closed.

但是,如果我们想多次重复使用同一个流怎么办?

如何实现?

是的,在 Java 8 个流中重用一个流

例如,对于任何终端操作,流在操作关闭时关闭。但是当我们在链中使用 Stream 时,我们可以避免这个异常:

终端正常运行:

Stream<String> stream =
    Stream.of("d2", "a2", "b1", "b3", "c")
        .filter(s -> s.startsWith("a"));

stream.anyMatch(s -> true);    // ok
stream.noneMatch(s -> true);   // exception

但是,如果我们使用:

Supplier<Stream<String>> streamSupplier =
    () -> Stream.of("d2", "a2", "b1", "b3", "c")
            .filter(s -> s.startsWith("a"));

streamSupplier.get().anyMatch(s -> true);   // ok
streamSupplier.get().noneMatch(s -> true);  // ok

这里是 .get() "constructs" 一个新的流,并且在到达这一点时不会重复使用。

干杯!

不,你不能重用 Stream,但是,如果重载堆 space 不是问题,你可以在终端操作之前保存流的内容以供重用, 使用 Stream.Builder。例如:

Stream<OriginalType> myStream = ...
Stream.Builder<SomeOtherType> copy = Stream.builder();
List<SomeOtherType> aList = myStream
     .filter(...)
     .map(...)     // eventually maps to SomeOtherType
     .peek(copy)   // pour values into a new Stream
     .collect(Collectors.toList());
Set<SomeOtherType> aSet = copy.build()
     .collect(Collectors.toSet());

可以将流链接在一起,在每个连续的 Stream 中添加一个新的 Stream.Builder 实例。

不是您要找的答案,但它确实避免了第二次执行管道操作的开销。它有自己的弱点,绑定到堆 space,但它没有 Holger 在他对 Supplier 解决方案的评论中建议的弱点——如果它是 Random 流, 它在第二次迭代中将具有相同的值。

Java 8 Streams 并不是真正的反应式,尽管它们非常实用。没有多个终端操作。提到 Supplier 的答案虽然让您编写看起来有多个终端操作的代码,但它们是独立生成的完全不同流上的终端。也就是说,时间复杂度没有改变。这相当于写

Stream getStream() {
   return Stream.of(....);
}

static void main() {
   Values values1 = getStream().collect();
   Values values2 = getStream().collect();
}

你想要多个终端操作的全部原因是为了节省计算,而不是让它看起来漂亮。查看 https://github.com/ReactiveX/RxJava,它提供了真正的反应对象。

没有。您不能多次使用流。 你可以做的是,你可以将流收集在一个列表中,然后你可以调用接受 lambda 的 map 和 forEach 函数。

List<String> list =
    Stream.of("test")
        .filter(s -> s.startsWith("a"))
        .collect(Collectors.toList());
list.forEach(item -> item);
list.map(item -> item);

一次对流元素进行 3 种不同的类似终端的操作的简单示例:

  • 显示它们,
  • 计数,
  • 计算它们的总和

当然这不是很优雅,但它有效:

    List<Integer> famousNumbers = List.of(0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55);
    Stream<Integer> numbersStream = famousNumbers.stream();
    Stream<Integer> numbersGreater5Stream = numbersStream.filter(x -> x > 5);

    var ref = new Object() {
        int counter = 0;
        int sum = 0;
    };

    numbersGreater5Stream.forEach(x -> {
        System.out.print(x + " ");
        ref.counter++;
        ref.sum += x;
    });

    System.out.println("\n" + ref.counter + " " + ref.sum);