Java 8 流向迭代器的迭代器导致对 hasNext() 的冗余调用
Java 8 Iterator to stream to iterator causes redundant call to hasNext()
我注意到在以下场景中有一些奇怪的行为:
迭代器 -> 流 -> 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!!!!
是的,这是正常的。冗余调用发生在 StreamSpliterators.AbstractWrappingSpliterator.fillBuffer()
中,它是从 stream.map(num -> num + 1).iterator()
返回的迭代器的 hasNext()
方法中调用的。来自JDK8 来源:
/**
* 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()
。如果为 true,它将下一个元素添加到 bufferSink
和 returns true,否则 returns false。当原始迭代器用完项目并且 returns 为 false 时,此方法调用 bufferSink.end()
并重试填充缓冲区,这导致冗余 hasNext()
调用。
在这种情况下,bufferSink.end()
没有任何效果,第二次填充缓冲区的尝试是不必要的,但正如源评论所解释的那样,它 "might trigger more elements" 在另一种情况下。这只是隐藏在 Java 8 个流的复杂内部工作中的一个实现细节。
我注意到在以下场景中有一些奇怪的行为:
迭代器 -> 流 -> 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!!!!
是的,这是正常的。冗余调用发生在 StreamSpliterators.AbstractWrappingSpliterator.fillBuffer()
中,它是从 stream.map(num -> num + 1).iterator()
返回的迭代器的 hasNext()
方法中调用的。来自JDK8 来源:
/**
* 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()
。如果为 true,它将下一个元素添加到 bufferSink
和 returns true,否则 returns false。当原始迭代器用完项目并且 returns 为 false 时,此方法调用 bufferSink.end()
并重试填充缓冲区,这导致冗余 hasNext()
调用。
在这种情况下,bufferSink.end()
没有任何效果,第二次填充缓冲区的尝试是不必要的,但正如源评论所解释的那样,它 "might trigger more elements" 在另一种情况下。这只是隐藏在 Java 8 个流的复杂内部工作中的一个实现细节。