我知道每当我们在一个流上调用任何终端方法
时,它都会关闭。
如果我们尝试在已关闭的流上调用任何其他终端函数,则会导致java.lang.IllegalStateException:stream has already been operated upon or closed
。
但是,如果我们想要多次重复使用相同的流
怎么办?
如何实现这个目标?
我知道每当我们在一个流上调用任何终端方法
时,它都会关闭。
如果我们尝试在已关闭的流上调用任何其他终端函数,则会导致java.lang.IllegalStateException:stream has already been operated upon or closed
。
但是,如果我们想要多次重复使用相同的流
怎么办?
如何实现这个目标?
在Java 8的流中,重复使用流是绝对不可行的。
例如,对于任何终端操作,当该操作关闭时,流也会被关闭。但是,在链式调用中使用流,可以避免出现此异常:
常规的终端操作:
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()
"构造"了一个新的流,并且每当它到达这个点时,不会重用先前的流。
干杯!
Random
实例或网络通道创建流,那么当您尝试重用这样的流时,应该发生什么情况?a)每次获取不同的值,导致不一致的结果,还是b)流应该缓冲所有值,以防第二次使用? - Holger不,你不能重复使用一个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
流,第二次迭代将具有相同的值。
Java 8的Streams虽然非常实用,但并不是真正的响应式编程。它们没有多个终端操作。提到Supplier的答案让你编写看起来像有多个终端操作的代码,但它们是在完全独立生成的流上的终端操作。也就是说,时间复杂度并没有改变。这相当于编写
Stream getStream() {
return Stream.of(....);
}
static void main() {
Values values1 = getStream().collect();
Values values2 = getStream().collect();
}
一个关于流元素同时进行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);
List<String> list =
Stream.of("test")
.filter(s -> s.startsWith("a"))
.collect(Collectors.toList());
list.forEach(item -> item);
list.map(item -> item);