【问题标题】:Java 8 Iterator to stream to iterator causes redundant call to hasNext()流式传输到迭代器的 Java 8 迭代器导致对 hasNext() 的冗余调用
【发布时间】:2015-05-16 05:20:18
【问题描述】:

我注意到以下场景中的一些奇怪行为:

迭代器 -> 流 -> map() -> 迭代器() -> 迭代

原始迭代器的 hasNext() 在已经返回 false 后被再次调用。

这正常吗?

package com.test.iterators;

import java.util.Iterator;
import java.util.Spliterators;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

public class TestIterator {

    private static int counter = 2;

    public static void main(String[] args) {

        class AdapterIterator implements Iterator<Integer> {
            boolean active = true;

            @Override
            public boolean hasNext() {
                System.out.println("hasNext() called");

                if (!active) {
                    System.out.println("Ignoring duplicate call to hasNext!!!!");
                    return false;
                }

                boolean hasNext = counter >= 0;
                System.out.println("actually has next:" + active);

                if (!hasNext) {
                    active = false;
                }

                return hasNext;
            }

            @Override
            public Integer next() {
                System.out.println("next() called");
                return counter--;
            }
        }

        Stream<Integer> stream = StreamSupport.stream(Spliterators.spliteratorUnknownSize(new AdapterIterator(), 0), false);
        stream.map(num -> num + 1).iterator().forEachRemaining(num -> {
            System.out.println(num);
        });
    }
}

如果我删除 map() 或将最终的 itearator() 替换为 count() 或 collect() 之类的东西,它可以在没有冗余调用的情况下工作。

输出

hasNext() called
actually has next:true
next() called
3
hasNext() called
actually has next:true
next() called
2
hasNext() called
actually has next:true
next() called
1
hasNext() called
actually has next:true
hasNext() called
Ignoring duplicate call to hasNext!!!!

【问题讨论】:

  • 我不会称之为“正常”,但在规范范围内。
  • 你的意思是因为 Iterator.hasNext() 必须是幂等的,对吧?
  • 对,next 可以被调用者喜欢的次数...

标签: iterator java-8 java-stream spliterator


【解决方案1】:

是的,这很正常。冗余调用发生在StreamSpliterators.AbstractWrappingSpliterator.fillBuffer() 中,由stream.map(num -&gt; num + 1).iterator() 返回的迭代器的hasNext() 方法调用。来自 JDK 8 源代码:

/**
 * If the buffer is empty, push elements into the sink chain until
 * the source is empty or cancellation is requested.
 * @return whether there are elements to consume from the buffer
 */
private boolean fillBuffer() {
    while (buffer.count() == 0) {
        if (bufferSink.cancellationRequested() || !pusher.getAsBoolean()) {
            if (finished)
                return false;
            else {
                bufferSink.end(); // might trigger more elements
                finished = true;
            }
        }
    }
    return true;
}

pusher.getAsBoolean() 的调用会在原始AdapterIterator 实例上调用hasNext()。如果为真,则将下一个元素添加到bufferSink 并返回真,否则返回假。当原始迭代器用完项目并返回 false 时,此方法会调用 bufferSink.end() 并重试填充缓冲区,这会导致多余的 hasNext() 调用。

在这种情况下,bufferSink.end() 没有效果,第二次尝试填充缓冲区是不必要的,但正如源注释所解释的,在另一种情况下它“可能会触发更多元素”。这只是隐藏在 Java 8 流的复杂内部工作原理中的一个实现细节。

【讨论】:

    猜你喜欢
    • 2010-12-30
    • 2023-03-06
    • 1970-01-01
    • 2012-10-14
    • 1970-01-01
    • 2011-11-22
    • 2018-07-28
    • 2015-11-25
    • 2020-12-15
    相关资源
    最近更新 更多