Java 8 Iterator转为Stream再转回Iterator会导致多余的hasNext()调用

5

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

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

原始迭代器的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()方法或者将最后的iterator()替换成类似于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!!!!

1
我不会称其为“正常”,但它在规范范围内。 - Holger
你的意思是因为Iterator.hasNext()必须是幂等的,对吗? - Nazaret K.
1
没错,next 可以被调用者随意调用... - Holger
1个回答

1

是的,这很正常。冗余调用发生在StreamSpliterators.AbstractWrappingSpliterator.fillBuffer()中,该方法从stream.map(num -> 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并返回true,否则返回false。当原始迭代器用尽并返回false时,此方法调用bufferSink.end()并重试填充缓冲区,这导致了多余的hasNext()调用。
在这种情况下,bufferSink.end()没有效果,第二次尝试填充缓冲区是不必要的,但正如源代码中所解释的那样,在另一种情况下,它可能会触发更多的元素。这只是Java 8流的复杂内部工作中深藏的实现细节。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接