Java 8 Iterator to stream to iterator causes redundant call to hasNext()

人走茶凉 提交于 2019-12-13 14:29:06

问题


I notice a bit of a strange behavior in the following scenario:

Iterator -> Stream -> map() -> iterator() -> iterate

The hasNext() of the original iterator is called an additional time after having already returned false.

Is this normal?

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);
        });
    }
}

If I either remove the map() or replace the final itearator() with something like count() or collect() it works without the redundant call.

Output

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:


Yes, this is normal. The redundant call happens in StreamSpliterators.AbstractWrappingSpliterator.fillBuffer(), which is called from the hasNext() method of the iterator returned by stream.map(num -> num + 1).iterator(). From the JDK 8 source:

/**
 * 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;
}

The call to pusher.getAsBoolean() calls hasNext() on the original AdapterIterator instance. If true, it adds the next element to bufferSink and returns true, otherwise it returns false. When the original iterator runs out of items and it returns false, this method calls bufferSink.end() and retries filling the buffer, which leads to the redundant hasNext() call.

In this case, bufferSink.end() has no effect and the second attempt to fill the buffer is unnecessary, but as the source comment explains, it "might trigger more elements" in another situation. This is just an implementation detail buried deep in the complex inner workings of Java 8 streams.



来源:https://stackoverflow.com/questions/29037100/java-8-iterator-to-stream-to-iterator-causes-redundant-call-to-hasnext

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!