I think an advantage of C#/.NET's MoveNext()
model over Java's hasNext()
model is that the former implies that some work may be done. The hasNext()
method implies a simple state check. But what if you are iterating a lazy stream, and you have to go out to a database to determine whether there are any results? In that case, the first call to hasNext()
may be a long blocking operation. The naming of the hasNext()
method can be very misleading as to what's actually going on.
For a real world example, this design issue bit me when implementing Microsoft's Reactive Extensions (Rx) APIs in Java. Specifically, for the iterator created by IObservable.asIterable()
to function properly, the hasNext()
method would have to block and wait for the next item/error/completion notification to arrive. That's hardly intuitive: the name implies a simple state check. Contrast that to the C# design, where MoveNext()
would have to block, which is not an altogether unexpected result when you are dealing with a lazily evaluated stream.
My point is this: the "next-iterator" model is preferable to the "this-iterator" model because the "this-iterator" model is often a lie: you may be required to pre-fetch the next element in order to check the state of the iterator. A design which communicates that possibility clearly is preferable, in my opinion. Yes, Java's naming conventions do follow the "next" model, but the behavioral implications are similar to those of the "this-iterator" model.
Clarification: Perhaps contrasting Java and C# was a poor choice, because Java's model is deceptive. I consider the most important distinction in the models presented by the OP to be that the "this-iterator" model decouples the "is valid" logic from the "retrieve current/next element" logic, while an ideal "next-iterator" implementation combines these operations. I feel it is appropriate to combine them because, in some cases, determining whether the iterator's state is valid requires prefetching the next element, so that possibility should be made as explicit as possible.
I do not see a significant design difference between:
while (i.isValid()) { // do we have an element? (implied as fast, non-blocking)
doSomething(i.current()); // retrieve the element (may be slow!)
i.next();
}
// ...and:
while (i.hasNext()) { // do we have an element? (implied as fast, non-blocking)
doSomething(i.next()); // retrieve the element (may be slow!)
}
But I do see a meaningful difference here:
while (i.hasNext()) { // do we have an element? (implied as fast, non-blocking)
doSomething(i.next()); // retrieve the element (may be slow!)
}
// ...and:
while (i.moveNext()) { // fetch the next element if it exists (may be slow!)
doSomething(i.current()); // get the element (implied as fast, non-blocking)
}
In both the isValid()
and hasNext()
examples, the operation that is implied to be a fast and non-blocking state check may in fact be a slow, blocking operation. In the moveNext()
example, most of the work is being done in the method you'd expect, regardless of whether you are dealing with an eagerly or lazily evaluated stream.