2015-03-13 35 views
3

我注意到有位在下列情況下一個奇怪的行爲:的Java 8迭代開始流入到迭代導致hasNext()冗餘呼叫

迭代器 - >流 - >地圖() - >中的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); 
     }); 
    } 
} 

如果我或者刪除地圖()或()像數或收集(更換最終itearator()),它的工作原理沒有冗餘呼叫。

輸出

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 2015-03-13 17:48:43

+0

你的意思是因爲Iterator.hasNext()必須是冪等的,對吧? – 2015-03-13 21:59:32

+1

對,可能會像調用者喜歡的那樣頻繁地調用下一個... – Holger 2015-03-16 08:41:06

回答

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()電話hasNext()原始AdapterIterator實例。如果爲true,則將下一個元素添加到bufferSink並返回true,否則返回false。當原始迭代器用完所有項並返回false時,此方法將調用bufferSink.end()並重試填充緩衝區,這會導致多餘的hasNext()調用。

在這種情況下,bufferSink.end()有沒有效果,第二次嘗試,填補了緩衝是不必要的,但作爲源註釋解釋說,它「可能會引發更多的元素」,在另一種情況。這只是一個埋在Java 8流的複雜內部工作中的實現細節。