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

9

我知道每当我们在一个流上调用任何终端方法时,它都会关闭。

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

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

如何实现这个目标?


4
这是一个冷酷的事实:你无法重复使用流。你可以拿到生成该流的输入,再次进行流操作。此外,根据你所面临的具体问题,仅进行一次流操作可能就能得到所需结果。 - Eugene
1
请参见https://dev59.com/QF4b5IYBdhLWcg3w-1-c,了解Java流一次性使用的原因。 - user140547
你为什么想要首先重复使用它们?你希望通过这样做实现什么目标? - Rogue
@Rogue,我在想这是否可能。 - Mehraj Malik
好的,那就算了:不。 - Rogue
5个回答

11

在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() "构造"了一个新的流,并且每当它到达这个点时,不会重用先前的流。

干杯!


4
好的,这是一个变通方法,但并非所要求的:如果我们想要多次重复使用同一数据流,该怎么办? - Markus Benko
那不是可重用性。 请阅读关于 get() 的最后一行。 - Mehraj Malik
在Java 8中,流基本上不能被重复使用,尤其是在终端操作中。答案是否定的。 - Vijayan Kani
3
@VijayanKani,实际上我喜欢这种情况下的供应商..可能很有用,但您需要更新回答,明确说明-不可能,但可以使用供应商... - Eugene
对于你的努力给予+1。但是很失望地听到Java不允许我们重复使用流。真奇怪!!! - Mehraj Malik
2
@Mehraj Malik:请注意,流并不总是由集合支持。例如,您可以从Random实例或网络通道创建流,那么当您尝试重用这样的流时,应该发生什么情况?a)每次获取不同的值,导致不一致的结果,还是b)流应该缓冲所有值,以防第二次使用? - Holger

1

不,你不能重复使用一个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流,第二次迭代将具有相同的值。


0

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提供的真正响应式对象。

0

一个关于流元素同时进行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);

0
不可以多次使用流。 你可以将流收集到列表中,然后调用接受 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);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接