当前位置: 首页 > 知识库问答 >
问题:

为什么flatMap()之后的filter()在Java流中“不完全”是惰性的?

孟彦
2023-03-14
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

共有1个答案

经俊茂
2023-03-14

TL;DR,这个问题在JDK-8075939中得到了解决,并在Java10中得到了修正(并在JDK-8225328中被移植到Java8中)。

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

@Override
final void forEachWithCancel(Spliterator<P_OUT> spliterator, Sink<P_OUT> sink) {
    do { } while (!sink.cancellationRequested() && spliterator.tryAdvance(sink));
}

它将在findfirst操作中调用。需要特别注意的是sink.cancellationRequested(),它允许在第一个匹配时结束循环。与[链接]相比较

@Override
public final <R> Stream<R> flatMap(Function<? super P_OUT, ? extends Stream<? extends R>> mapper) {
    Objects.requireNonNull(mapper);
    // We can do better than this, by polling cancellationRequested when stream is infinite
    return new StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE,
                                 StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT | StreamOpFlag.NOT_SIZED) {
        @Override
        Sink<P_OUT> opWrapSink(int flags, Sink<R> sink) {
            return new Sink.ChainedReference<P_OUT, R>(sink) {
                @Override
                public void begin(long size) {
                    downstream.begin(-1);
                }

                @Override
                public void accept(P_OUT u) {
                    try (Stream<? extends R> result = mapper.apply(u)) {
                        // We can do better that this too; optimize for depth=0 case and just grab spliterator and forEach it
                        if (result != null)
                            result.sequential().forEach(downstream);
                    }
                }
            };
        }
    };
}

为了说明其中的含义,当stream.iterate(0,i->i+1).findfirst()按预期工作时,stream.of("“).flatmap(x->stream.iterate(0,i->i+1)).findfirst()将陷入无限循环。

关于规范,大部分可以在

包规范的“流操作和管道”章节:

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

 类似资料:
  • 通过一组转换进行筛选和按键分组: 关于如何让它更好的性能,有什么想法吗?谢谢你的帮助!

  • 操作将 转换为包含每个输入元素的零个或多个元素的流,例如。 是否有相反的操作将几个元素分批添加到一个新的元素中? 事实并非如此。reduce(),因为这只产生一个结果 它不是collect(),因为它只填充一个容器(afaiu) 它不是forEach(),因为它只返回void,并且具有副作用 它存在吗?我可以用任何方式模拟它吗?

  • 对终端操作的任何调用都会关闭流,使其无法使用。这个‘特性’带走了很多权力。 我想这不是技术上的原因。这个奇怪的限制背后的设计考虑是什么? 编辑:为了演示我所讲的内容,请考虑以下C#中快速排序的实现:

  • 问题内容: 初始化的优点或区别是什么? 而不是简单地使用: (是:) 非常感谢您的帮助。 问题答案: 惰性存储属性vs存储属性 具有惰性属性而不是存储属性有一些优点。 仅当您读取该属性时,才执行与lazy属性关联的闭包。因此,如果出于某种原因未使用属性(可能是由于用户的某些决定),则可以避免不必要的分配和计算。 您可以使用存储属性的值填充惰性属性。 您可以在内部使用惰性属性关闭

  • 问题内容: 考虑以下代码: 当第一个URL够用时会被要求输入第二个URL吗? 我尝试了一个较小的示例,它看起来像预期的那样工作。即一个一个地处理数据,但是可以依靠这种行为吗?如果没有,在帮助之前打电话吗? 输出: 更新 :如果对实施很重要,请使用官方Oracle JDK8 答案 :根据下面的评论和答案,flatmap部分是惰性的。即完全读取第一个流,并且仅在需要时才读取下一个。渴望读取一个流,但是