为什么flatMap()之后的filter()是;不完全是",;Java流中的懒惰?

为什么flatMap()之后的filter()是;不完全是",;Java流中的懒惰?,java,lambda,java-8,java-stream,Java,Lambda,Java 8,Java Stream,我有以下示例代码: System.out.println( "Result: " + Stream.of(1, 2, 3) .filter(i -> { System.out.println(i); return true; }) .findFirst()

我有以下示例代码:

System.out.println(
       "Result: " +
        Stream.of(1, 2, 3)
                .filter(i -> {
                    System.out.println(i);
                    return true;
                })
                .findFirst()
                .get()
);
System.out.println("-----------");
System.out.println(
       "Result: " +
        Stream.of(1, 2, 3)
                .flatMap(i -> Stream.of(i - 1, i, i + 1))
                .flatMap(i -> Stream.of(i - 1, i, i + 1))
                .filter(i -> {
                    System.out.println(i);
                    return true;
                })
                .findFirst()
                .get()
);
结果如下:

1
Result: 1
-----------
-1
0
1
0
1
2
1
2
3
Result: -1
从这里我看到,在第一种情况下,
stream
确实表现得很懒散-我们使用
findFirst()
,所以一旦我们有了第一个元素,我们的过滤lambda就不会被调用。 然而,在使用
flatMap
s的第二种情况下,我们看到,尽管找到了满足过滤条件的第一个元素(它只是任何第一个元素,因为lambda总是返回true),流的进一步内容仍然通过过滤函数提供

我试图理解为什么它的行为是这样的,而不是像第一种情况那样在计算第一个元素后放弃。
任何有用的信息都将不胜感激。

输入流的元素将被一个接一个地懒散地消耗。第一个元素
1
,由两个
flatMap
转换为流
-1,0,1,0,1,2,1,2,3
,这样整个流只对应于第一个输入元素。嵌套流被管道急切地具体化,然后展平,然后馈送到
过滤器
阶段。这解释了您的输出

上述情况并不是源于一个基本的限制,但它可能会使嵌套流的全面惰性变得更加复杂。我怀疑要让它发挥作用将是一个更大的挑战


相比之下,Clojure的lazy Seq为每种嵌套级别提供了另一层包装。由于这种设计,当嵌套被执行到极限时,操作甚至可能会因StackOverflowerError而失败。

TL;DR,这在Java10中得到了解决,并在Java10中得到了修复(并在中向后移植到Java8

在研究实现(
ReferencePipeline.java
)时,我们看到了方法[]

推进一个项目的方法最终在子流上调用
forEach
,而不可能提前终止,
flatMap
方法开头的注释甚至说明了此缺失的功能

因为这不仅仅是一个优化问题,因为它意味着当子流无限大时,代码就会中断,我希望开发人员很快证明他们“可以做得更好”


为了说明其含义,虽然
Stream.iterate(0,i->i+1).findFirst()
按预期工作,
Stream.of(“”.flatMap(x->Stream.iterate(0,i->i+1)).findFirst()将以无限循环结束

关于规范,大部分可以在

:

中间操作返回一个新的流。他们总是懒惰

…懒惰还允许避免在不必要时检查所有数据;对于“查找长度超过1000个字符的第一个字符串”之类的操作,只需检查足够的字符串,以找到具有所需特征的字符串,而无需检查源中可用的所有字符串。(当输入流是无限的而不仅仅是大的时,这种行为变得更加重要。)

此外,一些操作被视为短路操作。中间操作是短路,当输入无限大时,可能会产生有限的流。如果终端操作在无限输入时可能在有限时间内终止,则终端操作为短路。在管道中进行短路操作是无限流处理在有限时间内正常终止的必要条件,但不是充分条件


很明显,短路操作不能保证有限时间终止,例如,当过滤器与处理无法完成的任何项目不匹配时,但是一个不支持在有限时间内通过简单地忽略操作的短路性质而终止的实现是远远不符合规范的。

关于无限子流的中断,当抛出一个中间值时,flatMap的行为变得更加令人惊讶(与端子相反)短路操作

当下列操作按预期工作时,打印出无限的整数序列

Stream.of("x").flatMap(_x -> Stream.iterate(1, i -> i + 1)).forEach(System.out::println);
以下代码仅打印出“1”,但仍然不终止:

Stream.of("x").flatMap(_x -> Stream.iterate(1, i -> i + 1)).limit(1).forEach(System.out::println);
我无法想象一个没有bug的规范阅读。

在我的免费库中,我引入了短路收集器。当使用短路收集器收集序列流时(如)源代码中只使用了一个元素。在内部,它是以一种非常肮脏的方式实现的:使用自定义异常来中断控制流。使用我的库,您的示例可以通过以下方式重写:

System.out.println(
        "Result: " +
                StreamEx.of(1, 2, 3)
                .flatMap(i -> Stream.of(i - 1, i, i + 1))
                .flatMap(i -> Stream.of(i - 1, i, i + 1))
                .filter(i -> {
                    System.out.println(i);
                    return true;
                })
                .collect(MoreCollectors.first())
                .get()
        );
结果如下:

-1
Result: -1

我同意其他人的看法,这是一个在上打开的错误。而且由于一年多后它仍未修复。我想向您推荐:


披露:我是AbacusUtil的开发人员。

不幸的是,
.flatMap()
并不懒惰。但是,这里有一个自定义的
flatMap
解决方法:

今天我也偶然发现了这个错误。行为并不是那么简单,因为像下面这样的简单情况可以很好地工作,但类似的生产代码不起作用

 stream(spliterator).map(o -> o).flatMap(Stream::of).flatMap(Stream::of).findAny()
对于那些不能再等几年迁移到JDK-10的人来说,有一个真正的延迟流。它不支持并行。它专门用于JavaScript翻译,但对我来说是可行的,因为接口是一样的

StreamHelper是基于集合的,但它很容易适应拆分器

虽然在Java 11中已修复,并后端口为10和8u222,但在Java 17中仍然存在
flatMap()
在使用
Stream.iterator()时不真正懒惰的边缘情况

这个

Ite
-1
Result: -1
N.println("Result: " + Stream.of(1, 2, 3).peek(N::println).first().get());

N.println("-----------");

N.println("Result: " + Stream.of(1, 2, 3)
                        .flatMap(i -> Stream.of(i - 1, i, i + 1))
                        .flatMap(i -> Stream.of(i - 1, i, i + 1))
                        .peek(N::println).first().get());

// output:
// 1
// Result: 1
// -----------
// -1
// Result: -1
 stream(spliterator).map(o -> o).flatMap(Stream::of).flatMap(Stream::of).findAny()