流非终结符操作+filter+findFirst

xKr*_*usX 4 java java-8 java-stream

我试图了解如何调用非终端流操作。

Stream.of("aaa", "bbb", "ccc")
        .map(s -> {
            System.out.println(s);
            return s.toUpperCase();
        });
//prints nothing

Stream.of("aaa", "bbb", "ccc")
        .map(s -> {
            System.out.println(s);
            return s.toUpperCase();
        })
        .forEach(s -> {});
//prints "aaa" "bbb" "ccc"
Run Code Online (Sandbox Code Playgroud)

这对我来说似乎很清楚。

第一个流未以终端操作结束,因此它不会调用非终端操作,并且不会打印任何内容。第二个有终端操作,因此所有元素都会被打印。

Stream.of("aaa", "bbb", "ccc")
        .map(s -> {
            System.out.println(s);
            return s.toUpperCase();
        })
        .findFirst();
//prints "aaa"

Stream.of("aaa", "bbb", "ccc")
        .map(s -> {
            System.out.println(s);
            return s.toUpperCase();
        })
        .filter(s -> s.startsWith("B"))
        .findFirst();
//prints "aaa" "bbb"
Run Code Online (Sandbox Code Playgroud)

这是我感到困惑的地方,尤其是最后一个。看起来该流在某种意义上是“向后”工作的。首先,它检查终端操作返回哪些元素,然后仅对这些元素执行中间操作。但最后一个怎么解释呢?看起来它对所有元素进行了映射,直到第一个与过滤器匹配的元素为止。在最后一个示例中,如果我替换findFirst()forEach(),它会打印所有元素,即使它最后只有一个元素。

对我来说似乎有点违反直觉。谁能给我正确的解释流如何识别它应该执行中间操作的元素?

Ale*_*nko 6

第一个流未以终端操作结束,因此它不会调用非终端操作,并且不会打印任何内容。

终端操作- 是一种生成并返回流执行结果的操作,或者作为副作用执行最终操作(在 的情况下forEach)。另一方面,中间操作是总是返回另一个流的流操作,它意味着以某种方式转换流管道。

当流幸运地执行终端操作时,它不会执行。这允许您在一种方法中创建流并将其分发给另一种方法,在另一种方法中它将附加一些附加操作(包括终端)并被执行。

首先,它检查终端操作返回哪些元素,然后仅对这些元素执行中间操作。

不,事情不是这样的。

流是惰性的,这意味着每个操作仅在需要时发生。Stream 的行为并不像循环链for

例如,如果我们有一个filter操作,后跟一个map操作,map仅当元素传递 时才应用filter,这意味着来自流源的元素会转到filter,如果它传递 ,则filter它会被分发到map,然后转到终端操作,如果流未终止,则源中的另一个元素会重复相同的步骤。

在您的示例中,Stream.of().map().filter().findFirst()流元素将从源中一个接一个地出现,直到filter找不到第一个通过的元素。当元素传递 时filter,流终止并findFirst返回该元素。

在大多数情况下,一次处理一个流元素,但当sorting()我们需要将所有流数据转储到内存中以对其进行排序时除外。

以下是Stream API 文档中的引用:

中间操作返回一个新流。他们总是很懒;执行诸如之类的中间操作filter()实际上并不执行任何过滤,而是创建一个新流,该流在遍历时包含与给定谓词匹配的初始流的元素。直到执行了管道的终止操作后,才开始遍历管道源。

...

延迟处理流可以显着提高效率;在诸如上面的 filter-map-sum 示例之类的管道中,过滤、映射和求和可以融合到数据的单次传递中,并具有最小的中间状态。惰性还允许在不必要时避免检查所有数据;对于诸如“查找第一个超过 1000 个字符的字符串”之类的操作,只需检查足够的字符串即可找到具有所需特征的字符串,而无需检查源中可用的所有字符串。

某些操作独立于其他元素处理每个元素,它们不保留有关先前遇到的元素的信息并称为无状态(例如:filtermapflatMap等)。另一方面,一些操作(如distincttakeWhilesorted等)需要有关先前处理的元素的信息才能对下一个元素执行操作,即它们需要保持状态,因此称为有状态的

另外,看看这个与流处理相关的问题。