7

I'm attempting to use the number of iterations from an iterator as a counter, but was wondering the ramifications of doing so.

private int length(Iterator<?> it) {
    int i = 0;

    while(it.hasNext()) {
        it.next();
        i++;
    }

    return i;
}

This works fine, but I'm worried about what the iterator may do behind the scenes. Perhaps as I'm iterating over a stack, it pops the items off the stack, or if I'm using a priority queue, and it modifies the priority.

The javadoc say this about iterator:

next
E next()
Returns the next element in the iteration.
Returns:
the next element in the iteration
Throws:
NoSuchElementException - if the iteration has no more elements

I don't see a guarantee that iterating over this unknown collection won't modify it. Am I thinking of unrealistic edge cases, or is this a concern? Is there a better way?

jgawrych
  • 3,322
  • 1
  • 28
  • 38

4 Answers4

7

The Iterator simply provides an interface into some sort of stream, therefore not only is it perfectly possible for next() to destroy data in some way, but it's even possible for the data in an Iterator to be unique and irreplaceable.

We could come up with more direct examples, but an easy one is the Iterator in DirectoryStream. While a DirectoryStream is technically Iterable, it only allows one Iterator to be constructed, so if you tried to do the following:

Path dir = ...
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
  int count = length(stream.iterator());
  for (Path entry: stream) {
    ...
  }
}

You would get an exception in the foreach block, because the stream can only be iterated once. So in summary, it is possible for your length() method to change objects and lose data.

Furthermore, there's no reason an Iterator has to be associated with some separate data-store. Take for example an answer I gave a few months ago providing a clean way to select n random numbers. By using an infinite Iterator we are able to provide, filter, and pass around arbitrarily large amounts of random data lazily, no need to store it all at once, or even compute them until they're needed. Because the Iterator doesn't back any data structure, querying it is obviously destructive.

Now that said, these examples don't make your method bad. Notice that the Guava library (which everyone should be using) provides an Iterators class with exactly the behavior you detail above, called size() to conform with the Collections Framework. The burden is then on the user of such methods to be aware of what sort of data they're working with, and avoid making careless calls such as trying to count the number of results in an Iterator that they know cannot be replaced.

Community
  • 1
  • 1
dimo414
  • 47,227
  • 18
  • 148
  • 244
4

As far as I can tell, the Collection specification does not explicitly state that iterating over a collection does not modify it, but no classes in the standard library show that behaviour (actually at least one does, see dimo414's answer), so any class that did would be highly suspect. I don't think you need to worry about this.

Note that the Guava library implements Iterators.size() and Iterables.size() in the same way that you are, so clearly they find it safe in the general case.

MikeFHay
  • 8,562
  • 4
  • 31
  • 52
  • 1
    +1. Note that even if `java.util.Iterator` did specify something like that, it wouldn't actually be a binding requirement. It happens pretty often that a class doesn't actually conform to the contracts of its interfaces. (For example, the JDK's `java.util.IdentityHashMap` completely, intentionally, and documentedly violates the general contract of `java.util.Map`.) – ruakh Jan 12 '14 at 23:19
  • 1
    Objects other than children of `Collection` can have or be `Iterator`s. – dimo414 Jan 12 '14 at 23:27
3

No, iterating over a collection will not modify the collection. The Iterator class does have a remove() method, which is the only safe way of removing an element from a collection during iteration. But simply calling hasNext() and next() will not modify the collection.

Keep in mind that if you modify the object returned by next(), those changes will be present in your collection.

  • 1
    That is a reasonable behaviour but a collection that would get "modified" when being iterated would still be compliant with the iteration contract (apparently), which is what the question is about. – assylias Jan 12 '14 at 22:57
  • Iterating over a collection (something in the Collections Framework) will not modify the collection, however this does not mean that some arbitrarily defined `Iterator` or `Iterable` could not do so if it chose to. Consider an `Iterator` that returns random numbers; once `next()` is called, that number is effectively "gone". – dimo414 Jan 12 '14 at 23:24
0

Think about it -- methods that return things are (if they are written correctly) accessor methods, meaning that they just return data. They do not modify it (they are not mutator methods).

Here's an example I had on my disk of how an iterator might be implemented. As you can see, no values are actually modified.

public class ArraySetIterator implements Iterator
{
    private int nextIndex;
    private ArraySet theArraySet;

    public ArraySetIterator (ArraySet a)
    {
        this.nextIndex = 0;
        this.theArraySet = a;
    }

    public boolean hasNext ()
    {
        return this.nextIndex < this.theArraySet.size();
    }

    public Object next()
    {
        return this.theArraySet.get(this.nextIndex++);
    }
}
  • 1
    Yes *generally* `Iterator` does not modify. OP is asking about exceptional cases. An example `Iterator` that does not modify its data is irrelevant. There is no set-in-stone rule that methods with return values never change state. – dimo414 Jan 12 '14 at 23:04