如何在 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