简体   繁体   中英

Java Iterator implementation - next() and hasNext() enforcing order

I have an implementation of java.util.Iterator which requires that the call to next() should always be proceeded by a call to hasNext() . (This is because results are returned asynchronosly in a multi threaded environment and it is never clear how many more results there might be).

Would it be 'correct' to properly document this in the JavaDoc and then throw a RuntimeException if this was violated. Or is this stretching the Iterator interface a little too far?

All thoughts appreciated?

我可能在这里遗漏了一些东西,但为什么不在你的实现内部调用hasNext()呢?

Requiring that hasNext() be called before next() violates the iterator contract. You really should rewrite it so that next() simply throws a NoSuchElementException if there is no element to return.

I imagine you're doing something like this:

class IteratorImpl<T> implements Iterator<T> {
  private Source<T> source = ...
  private T next = null;

  public boolean hasNext() {
    if(next == null) {
      next = source.poll();
    }
    return next != null;
  }

That sounds OK to me. I can't imagine a situation where you'd want to use next without hasNext - it would be a recipe for exceptions.


EDIT:

The doc for hasNext() says:

Returns true if the iteration has more elements. (In other words, returns true if next would return an element rather than throwing an exception.)

To me, the implementation does not violate the contract. However, I would (as Fabian Steeg implies ) still implement next() as:

  public T next() {
    if(!hasNext()) {
      throw new NoSuchElementException();
    }
    T ret = next;
    next = null;
    return ret;
  }

I mean, what does that check really cost you?

You must check and throw a NoSuchElementException as per the API contract . Either testing on !hasNext() or next == null will meet this criteria, I believe, but I would favour the former.

If someone is catching NoSuchElementException instead of calling hasNext() , you probably have bigger problems.

If your hasNext() and next() calls aren't in a synchronized block/method, it is not guaranteed that you will have elements even if you call hasNext() before next() .

The contract of the Iterator interface is that NoSuchElementException should be thrown if there are no more elements. So proceed with the next() method until such an exception arises.

That said, take a look at the java.util.concurrent package - it has concurrent collections whose iterators may help you - ie you can use these collections and iterators instead of implementing your own.

I would rather throw an exception from next() , when there are no more elements. In a multi-threaded environment hasNext() is pretty useless anyway.

What you can do is implement the Iterator interface yourself (if you need to interface with other API) and ask your clients to implement your own stricter interface.

I created such a class in my functional programming library in order to easily implement an Iterator around a ResultSet in a project at work.

My class EasierIterator implements the Iterator interface while requiring that the client implements a simpler interface based on moveNext() / getCurrent() . It actually does the caching of the current item for you.

You could do something similar to the below, whereby you delegate the underlying data fetch to a private method, and implement hasNext() and next() to react differently to the absence of data. This has the advantage that you can repeatedly call next() without calling hasNext() first, and hence do not violate the contract of Iterator .

public class IteratorImpl<T> implements Iterator<T> {
  private final Source<T> source;
  private T next;

  public synchronized boolean hasNext() {
    tryGetNext();
    return next != null;
  }

  public synchronized T next() {
    tryGetNext();

    if (next == null) {
      throw new NoSuchElementException();
    } 

    return next;
  }

  private void tryGetNext() {
    if (next != null) {
      next = source.poll();
    }
  }
}

EDIT: In this answer I tried to argue that it is allowed what the question asks about. But I overlooked one sentence in the Iterator.hasNext documentation which invalidates my whole reasoning:

In other words, returns true if next() would return an element rather than throwing an exception.

This seems to imply that calling next repeatedly until hasNext returns true and calling next until you get a NoSuchElementException should return the same sequence of elements.

Thus it seems that what the question asks about is not allowed.

Original answer

This is an attempt on a specification lawyer type of answer. For clarity I'll restate the question in a compact form:

Is it allowed by the Iterable specification to throw a NoSuchElementException when Iterator.next is called without a preceding call to Iterator.hasNext , even if an element would have been return had Iterator.hasNext been called first?

Discussion

The documentation for Iterator.hasNext states:

Returns true if the iteration has more elements.

And for Iterator.next :

Throws: NoSuchElementException - if the iteration has no more elements

Apparently it's allowed to throw NoSuchElementException when "the iteration has no more elements", but not before that. That should coincide with when hasNext returns false.

This leads to the question: Exactly what does the documentation mean with "the iteration" and "elements"? The Iterator documentation does not give an answer to that, which gives some wiggling space for implementers.

In my view there are two possible interpretations:

  1. From the perspective of the iterator interface itself the only existing concept of "iteration" is "as long as hasNext returns true". That implies that if the client calls next before hasNext they doesn't know if there are more elements, it's undefined.

    It is hence allowed by the specification for the iterator implementer to decide that the iteration has finished. So the answer to the question is yes.

  2. But the documentation on Iterable.iterator also mention "elements":

    Returns an iterator over elements of type T .

    So what does "elements" mean here? Does it mean "all elements in the collection implementing Iterable ? No, it doesn't say so, and not all iterables even have a fixed set of elements.

    What "elements" mean for some particular iterable is left for the implementer to decide. A valid definition of "elements" for an iterator could be " all of the elements in the collection, OR, all elements before the client decides to call next before hasNext ".

    So this case also leads to the conclusion that the answer to the question is yes. (But please see the note on the end!)

Conclution

The documentation is not really clear, but it seem like the answer to the question is: Yes, this is allowed.

Note

In case that an iterator does what the question asks about that behaviour should be documented of course. But other iterables should also document over which elements their iterators are.

For example, the ArrayList.iterator documentation clearly states which elements the iterator is over:

Returns an iterator over the elements in this list in proper sequence.


Final note: Yes, I am crazy to spend so much time on this.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM