为什么我必须链接Java中的Stream操作? [英] Why do I have to chain Stream operations in Java?

查看:71
本文介绍了为什么我必须链接Java中的Stream操作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我认为我以某种方式研究的所有资源都强调一个流只能被消耗一次,而消耗是通过所谓的终端操作来完成的(这对我来说很清楚).

I think all of the resources I have studied one way or another emphasize that a stream can be consumed only once, and the consumption is done by so-called terminal operations (which is very clear to me).

出于好奇,我尝试了以下方法:

Just out of curiosity I tried this:

import java.util.stream.IntStream;

class App {
    public static void main(String[] args) {
        IntStream is = IntStream.of(1, 2, 3, 4);
        is.map(i -> i + 1);
        int sum = is.sum();
    }
}

最终会引发运行时异常:

which ends up throwing a Runtime Exception:

Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
    at java.util.stream.IntPipeline.reduce(IntPipeline.java:456)
    at java.util.stream.IntPipeline.sum(IntPipeline.java:414)
    at App.main(scratch.java:10)

这是很平常的事,我想念一些东西,但仍然想问:据我所知,map是一个中间(和惰性)操作,并且对Stream本身没有任何作用.仅当调用终端操作sum(这是一个急切的操作)时,Stream才会消耗 操作.

This is usual, I am missing something, but still want to ask: As far as I know map is an intermediate (and lazy) operation and does nothing on the Stream by itself. Only when the terminal operation sum (which is an eager operation) is called, the Stream gets consumed and operated on.

但是为什么我必须将它们链接起来?

But why do I have to chain them?

is.map(i -> i + 1);
is.sum();

is.map(i -> i + 1).sum();

?

推荐答案

执行此操作时:

int sum = IntStream.of(1, 2, 3, 4).map(i -> i + 1).sum();

每个链式方法都在链中上一个方法的返回值上被调用 .

Every chained method is being invoked on the return value of the previous method in the chain.

因此,IntStream.of(1, 2, 3, 4)返回时调用mapmap(i -> i + 1)返回时调用sum.

So map is invoked on what IntStream.of(1, 2, 3, 4) returns and sum on what map(i -> i + 1) returns.

您不必链接流方法,但是与使用以下等效代码相比,它更具可读性且不易出错:

You don't have to chain stream methods, but it's more readable and less error-prone than using this equivalent code:

IntStream is = IntStream.of(1, 2, 3, 4);
is = is.map(i -> i + 1);
int sum = is.sum();

与您在问题中显示的代码不同:

Which is not the same as the code you've shown in your question:

IntStream is = IntStream.of(1, 2, 3, 4);
is.map(i -> i + 1);
int sum = is.sum();

如您所见,您将忽略map返回的引用.这是错误的原因.

As you see, you're disregarding the reference returned by map. This is the cause of the error.

编辑(根据评论,感谢@IanKemp指出了这一点):实际上,这是错误的外部原因.如果您停止考虑,map必须对流本身做内部的操作,否则,终端操作将如何触发在每个元素上传递给map的转换?我同意中间操作是惰性的,即,在调用中间操作时,它们对流的元素没有任何作用.但是在内部,它们必须在流管道本身中配置一些状态,以便以后可以应用.

EDIT (as per the comments, thanks to @IanKemp for pointing this out): Actually, this is the external cause of the error. If you stop to think about it, map must be doing something internally to the stream itself, otherwise, how would then the terminal operation trigger the transformation passed to map on each element? I agree in that intermediate operations are lazy, i.e. when invoked, they do nothing to the elements of the stream. But internally, they must configure some state into the stream pipeline itself, so that they can be applied later.

尽管我不知道全部细节,但是发生的事情是,从概念上讲,map至少在做两件事:

Despite I'm not aware of the full details, what happens is that, conceptually, map is doing at least 2 things:

  1. 它正在创建并返回一个新流,该流将作为参数传递的函数保存在某处,以便稍后在调用终端操作时可以将其应用于元素.

  1. It's creating and returning a new stream that holds the function passed as an argument somewhere, so that it can be applied to elements later, when the terminal operation is invoked.

它还在旧流实例(即已被调用的那个)上设置一个标志,指示该流实例不再代表管道的有效状态.这是因为保存传递给map的函数的新的更新状态现在已由返回的实例封装. (我相信jdk团队可能已经做出了此决定,以使错误尽早出现,例如,通过引发早期异常,而不是让管道继续处于不具有该功能的无效/旧状态,被应用,从而使终端操作返回意外结果.)

It is also setting a flag to the old stream instance, i.e. the one which it has been called on, indicating that this stream instance no longer represents a valid state for the pipeline. This is because the new, updated state which holds the function passed to map is now encapsulated by the instance it has returned. (I believe that this decision might have been taken by the jdk team to make errors appear as early as possible, i.e. by throwing an early exception instead of letting the pipeline go on with an invalid/old state that doesn't hold the function to be applied, thus letting the terminal operation return unexpected results).

稍后,当在此实例上被标记为无效的实例上调用终端操作时,您将得到该IllegalStateException.上面的两个项目配置了导致错误的深层内部原因.

Later on, when a terminal operation is invoked on this instance flagged as invalid, you're getting that IllegalStateException. The two items above configure the deep, internal cause of the error.

查看所有这一切的另一种方法是通过中间操作或终端操作确保Stream实例仅操作一次.在这里您违反了此要求,因为您在同一实例上调用mapsum.

Another way to see all this is to make sure that a Stream instance is operated only once, by means of either an intermediate or a terminal operation. Here you are violating this requirement, because you are calling map and sum on the same instance.

实际上,

In fact, javadocs for Stream state it clearly:

一个流只能操作一次(调用中间流或终端流操作).例如,这排除了分叉"流,其中相同的源馈送了两个或多个管道,或同一流的多次遍历.如果流实现检测到正在重用该流,则可能抛出IllegalStateException.但是,由于某些流操作可能会返回其接收者而不是新的流对象,因此可能无法在所有情况下都检测到重用.

A stream should be operated on (invoking an intermediate or terminal stream operation) only once. This rules out, for example, "forked" streams, where the same source feeds two or more pipelines, or multiple traversals of the same stream. A stream implementation may throw IllegalStateException if it detects that the stream is being reused. However, since some stream operations may return their receiver rather than a new stream object, it may not be possible to detect reuse in all cases.

这篇关于为什么我必须链接Java中的Stream操作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆