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

Posted

技术标签:

【中文标题】如何在 Java 流上调用多个终端操作【英文标题】:How to call multiple terminal operation on a Java stream 【发布时间】:2017-07-22 00:01:54 【问题描述】:

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

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

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

如何做到这一点?

【问题讨论】:

这是一个冷酷的事实:您不能重复使用流。您可以获取从中获取流的 input 并再次流。此外,根据您所面临的确切问题,您可能只需流式传输一次即可获得所需的结果。 请参阅 ***.com/questions/28459498/… 了解基本原理 一开始为什么要重复使用它们?你希望通过这样做来实现什么? @Rogue,只是在想是否可能。 好吧:不。 【参考方案1】:

是的,在 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() 在这里“构造”了一个新的流,并且在达到这一点时不会重用。

干杯!

【讨论】:

嗯,这是一种解决方法,但不是所要求的:what if we want to reuse *the same stream* more than once 这不是可重用性。请阅读您关于 get() 的最后一行。 在 Java 8 中,流基本上不能被重用,实际上在终端操作中是不能重用的。答案是否定的。 @VijayanKani 在这种情况下,我实际上很喜欢供应商.. 可能有用,但您需要更新明确表示的答案 - 这是不可能的,但您可以做供应商...跨度> @Mehraj Malik:只要考虑一下 Streams 并不总是由 Collection 支持。例如,您可以从Random 实例或网络通道创建一个流,那么当您尝试重用这样的流时会发生什么,a)每次获取不同的值,产生不一致的结果或 b)流应该缓冲所有值,以防第二次使用?【参考方案2】:

不,您不能重用Stream,但是,如果不考虑重载堆空间,您可以使用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 实例。

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

【讨论】:

【参考方案3】:

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

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


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

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

【讨论】:

【参考方案4】:

没有。您不能多次使用流。 您可以做的是,您可以将流收集到一个列表中,然后您可以调用接受 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);

【讨论】:

【参考方案5】:

一次对流元素进行 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);

【讨论】:

以上是关于如何在 Java 流上调用多个终端操作的主要内容,如果未能解决你的问题,请参考以下文章

流上的 reduce() 操作似乎正在修改数据源(列表)Stream API Java 8

如何在 Java 的 linux 终端中交互地执行多个命令?

java调用linux终端命令,如何使终端不直接退出

Spark Streaming:如何在流上加载管道?

如何在 webrtc 的画布流上添加音频流

Java如何执行操作系统的CMD命令行