66

Is it generally considered bad practice to provide Iterator implementations that are "infinite"; i.e. where calls to hasNext() always(*) return true?

Typically I'd say "yes" because the calling code could behave erratically, but in the below implementation hasNext() will return true unless the caller removes all elements from the List that the iterator was initialised with; i.e. there is a termination condition. Do you think this is a legitimate use of Iterator? It doesn't seem to violate the contract although I suppose one could argue it's unintuitive.

public class CyclicIterator<T> implements Iterator<T> {
  private final List<T> l;
  private Iterator<T> it;

  public CyclicIterator<T>(List<T> l) {
    this.l = l;
    this.it = l.iterator();
  }

  public boolean hasNext() {
    return !l.isEmpty();
  }

  public T next() {
    T ret;

    if (!hasNext()) {
      throw new NoSuchElementException();
    } else if (it.hasNext()) {
      ret = it.next();
    } else {
      it = l.iterator();
      ret = it.next();
    }

    return ret;
  }

  public void remove() {
    it.remove();
  }
}

(Pedantic) EDIT

Some people have commented how an Iterator could be used to generate values from an unbounded sequence such as the Fibonacci sequence. However, the Java Iterator documentation states that an Iterator is:

An iterator over a collection.

Now you could argue that the Fibonacci sequence is an infinite collection but in Java I would equate collection with the java.util.Collection interface, which offers methods such as size() implying that a collection must be bounded. Therefore, is it legitimate to use Iterator as a generator of values from an unbounded sequence?

Adamski
  • 54,009
  • 15
  • 113
  • 152
  • I'm not as familiar with Java, but will this actually work? Doesn't it throw an exception if you remove things from the List while iterating over it? – JSBձոգչ Apr 12 '10 at 14:04
  • 1
    Fantastic question. Looking forward to some interesting answers. – Mark Canlas Apr 12 '10 at 14:06
  • 3
    Iterator implementations are typically designed to fail-fast in that removing items *directly* from the underlying Collection will result in a ConcurrentModificationException being thrown by the Iterator. However, removing items via Iterator's remove() method is a safe approach. – Adamski Apr 12 '10 at 14:06
  • 2
    google-collections/guava has a static method in the Iterators class for creating an Iterator that cycles infinitely over all the elements in an underlying Iterator, by the way. – ColinD Apr 12 '10 at 14:24
  • 2
    From the Java 6 `Collection` Javadoc: _"If this collection contains more than `Integer.MAX_VALUE` elements, returns `Integer.MAX_VALUE`."_ So it would not be violating the contract of `size` to create a `FibonacciList` implementing `List` that returns `Integer.MAX_SIZE`. However, you wouldn't be able to access more than than the `Integer.MAX_SIZE`-th element through the `get` method, so I doubt such a collection would be of much practical use! – Iain Samuel McLean Elder Apr 14 '10 at 05:29
  • 1
    isme: an infinite collection could be accessed using the iterator! – Mechanical snail Jul 06 '11 at 15:25
  • @JSBձոգչ It only throws an exception if you remove elements from a collection through some other method than the iterator's `remove()` method. – AJMansfield Sep 12 '13 at 15:14

11 Answers11

76

I think it is entirely legitimate - an Iterator is just a stream of "stuff". Why should the stream necessarily be bounded?

Plenty of other languages (e.g. Scala) have the concept of unbounded streams built in to them and these can be iterated over. For example, using scalaz

scala> val fibs = (0, 1).iterate[Stream](t2 => t2._2 -> (t2._1 + t2._2)).map(_._1).iterator
fibs: Iterator[Int] = non-empty iterator

scala> fibs.take(10).mkString(", ") //first 10 fibonnacci numbers
res0: String = 0, 1, 1, 2, 3, 5, 8, 13, 21, 34

EDIT: In terms of the principle of least surprise, I think it depends entirely on the context. For example, what would I expect this method to return?

public Iterator<Integer> fibonacciSequence();
oxbow_lakes
  • 133,303
  • 56
  • 317
  • 449
  • 4
    +1 A very good point. It changed my mind. – Justin Niessner Apr 12 '10 at 14:06
  • The concept as such is definitely legitimate. An important question is still if such an `Iterator` would violate the principle of least suprise **specifically** in Java. – Joachim Sauer Apr 12 '10 at 14:09
  • @Joachim - I think it entirely depends on the context. See edit – oxbow_lakes Apr 12 '10 at 14:12
  • 1
    @oxbow: of course and in that case I'd have no problem whatsoever. The thing that could get messy is if an `Iterable` would return such an `Iterator` (see my answer). There is an unspoken assumption that an `Iterable` basically behaves like a limited `Collection` i.e. they return a finite number of objects. – Joachim Sauer Apr 12 '10 at 14:16
  • 3
    The principle is not a dogma. Feel free to mke an exception BUT MAKE IT OBVIOUS, that it's an exception. Like, document this behavior in bold on top of the function description and make the name self-explainatory. – SF. Apr 12 '10 at 15:20
  • +1. I've done the same thing with the yield keyword in C#. – TrueWill Apr 12 '10 at 16:38
19

The whole point of an Iterator is that it is lazy, i.e. that it gives you only as many objects as you ask for. If a user asks for all objects of an infinite Iterator, it's their problem, not yours.

Neuron
  • 5,141
  • 5
  • 38
  • 59
Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
7

While I too think that it's legitimate, I'd like to add that such an Iterator (or more precisely: an Iterable producing such an Iterator) would not play well with the enhanced for-loop (a.k.a for-each-loop):

for (Object o : myIterable) {
   ...
}

Since the code inside an enhanced for loop has no direct access to the iterator, it couldn't call remove() on the iterator.

So to end the looping it would have to do one of the following:

  • Get access to the internal List of the Iterator and remove objects directly, possibly provoking a ConcurrentModificationException
  • use break to exit the loop
  • "use" an Exception to exit the loop
  • use return to leave the loop

All but the last of those alternatives aren't exactly the best way to leave an enhanced for-loop.

The "normal" for-loop (or any other loop together with an explicit Iterator variable) would work just fine, of course:

for (Iterator it = getIterator(); it.hasNext();) {
  Object o = it.next()
  ...
}
Joachim Sauer
  • 302,674
  • 57
  • 556
  • 614
  • 4
    ... or use "return" to get out of the loop, which might actually be a nice way to get out if the intent of the loop is to find a specific element. – Rasmus Kaj Apr 12 '10 at 15:15
  • 4
    I don’t agree. At least `break` (and `return`) are completely acceptable ways to leave a loop. – Konrad Rudolph Apr 12 '10 at 15:17
  • 6
    @Konrad: they are acceptable, but the enhanced for-loop **strongly** implies that I want to iterate over *every* element the `Iterator` provides. Similarly almost no-one would expect a `break` or `return` in a loop that starts like this: `for (int i=0; i<10; i++)`. – Joachim Sauer Apr 12 '10 at 15:20
  • Use break or return. If you have an Iterable, you must assume that it's an unending stream of Ts unless you have some other guarantee that it's not. – James Moore Aug 24 '10 at 21:54
7

This may be semantics, but the iterator should diligently return the next item without regard to when it will end. Ending is a side effect for a collection, though it may seem like a common side effect.

Still, what's the difference between infinite, and a collection with 10 trillion items? The caller either wants them all, or he doesn't. Let the caller decide how many items to return or when to end.

I wouldn't say the caller couldn't use the for-each construct. He could, as long as he wants all the items.

Something in the documentation for the collection like "may be an infinite collection" would be appropriate, but not "infinite iterator".

Marcus Adams
  • 53,009
  • 9
  • 91
  • 143
  • 2
    Yes! Think about a webserver: it's just a for each loop over an infinite stream of HTTP requests. Why the hell should it terminate? In fact, if a webserver *does* terminate, we usually call that a "crash" and get all upset about it. You *want* the webserver to be an infinite loop! – Jörg W Mittag Apr 12 '10 at 15:15
  • I'm with you. If something returns an Iterable, callers must assume that it won't terminate. They should only assume that they can operate on each item as it comes to them. – James Moore Aug 24 '10 at 21:51
4

It is a perfectly legitimate use - as long as it is properly documented.

Using the name CyclicIterator is a good idea, as it infers that looping on the iterator might well possibly be infinite if the loop exit case is not properly defined.

Yuval Adam
  • 161,610
  • 92
  • 305
  • 395
  • 2
    Given this would you say that an API should return a CyclicIterator rather than an Iterator in situations where this is used? It makes it very explicit but makes it impossible to change the implementation transparently, and doesn't actually provide any additional methods in its implementation. – Adamski Apr 12 '10 at 14:09
  • Given the appropriate use-case I would consider that - absolutely. – Yuval Adam Apr 12 '10 at 15:24
  • Doesn't *CyclicIterator* implies the iterator runs... well in cycles? What's wrong with *InfiniteIterator*? – whiskeysierra Apr 12 '10 at 23:10
  • Ok, that was a dumb comment... *head-slapping-myself*. Next time i'll read the question before writing bullshit. – whiskeysierra Apr 12 '10 at 23:13
4

An infinite iterator is very useful when you create infinite data, e.g. linear recurrent sequences like the Fibonacci sequence.

So it is totally fine to use such.

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • I've been thinking about this and am not sure whether it goes against the definition that an Iterator is "An iterator over a collection". See my latest edit. – Adamski Apr 12 '10 at 16:00
4

Yes. You just need a different criteria for when to stop iterating.

The concept of infinite lists is very common in lazy evaluation languages like Haskell and leads to completely different program designs.

Thorbjørn Ravn Andersen
  • 73,784
  • 33
  • 194
  • 347
3

Actually, an Iterator comes from an Iterable, not a Collection, whatever the JavaDoc API says. The latter extends the former, and that's why it can generate an Iterator as well, but it's perfectly reasonably for an Iterable not to be a Collection. In fact, there are even a few Java classes which implement Iterable but not Collection.

Now, as for expectations... of course an infinite iterator must not be made available without a clear indication that this happens to be the case. But, sometimes, expectations can be deceiving. For instance, one might expect an InputStream to always come to an end, but that really isn't a requirement of it. Likewise, an Iterator returning lines read from such a stream might never end.

Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
2

Consider whether you have a generator function. An iterator may be a reasonable interface to a generator, or it might not, as you've noticed.

On the other hand, Python has generators which do terminate.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
1

I can imagine any number of uses for an infinite iterator. Like, what about a program iterating through status messages being sent by another server or a background process. While in real life the server will presumably stop eventually, from the program's point of view it might just keep reading until it is stopped by something unrelated to the iterator reaching its end.

It would certainly be unusual and, as others have said, should be carefully documented. But I wouldn't declare it unacceptable.

Jay
  • 26,876
  • 10
  • 61
  • 112
  • 1
    Additional thought: While I have already expressed agreement with those who say that such a thing should be carefully documented, it occurs to me that this is probably less of a problem than I at first thought: If someone is using an iterator -- or any other construct -- without knowing what it is supposed to return, he's in for trouble no matter what! – Jay Apr 13 '10 at 16:16
1

If you have an object that needs an iterator, and you happen to give it an infinite iterator, then all should be well. Of course, that object should never require the iterator to stop. And that is the potentially bad design.

Think of a music player. You tell it to start playing tracks in "continuous play mode" and it plays tracks until you tell it to stop. Maybe you implement this continuous play mode by shuffling a playlist forever... until the user presses "stop". In this case, MusicPlayer needs to iterator over a playlist, that might be Collection<Track>, forever. In this case, you'd want either a CyclicIterator<Track> as you've built or a ShuffleIterator<Track> that randomly picks the next track and never runs out. So far, so good.

MusicPlayer.play(Iterator<Track> playlist) wouldn't care whether the play list ends or not. It works both with an iterator over a list of tracks (play to the end, then stop) and with a ShuffleIterator<Track> over a list of tracks (pick a random track forever). All still good.

What if someone tried to write MusicPlayer.playContinuously()--which expects to play tracks "forever" (until someone presses "stop"), but accepts an Iterator<Track> as the parameter? That would be the design problem. Clearly, playContinuously() needs an infinite Iterator, and if it accepts a plain Iterator as its parameter, then that would be bad design. It would, for example, need to check hasNext() and handle the case where that returns false, even though it should never happen. Bad.

So implement all the infinite iterators you want, as long as their clients don't depend on those iterators ending. As if your client depends on the iterator going on forever, then don't give it a plain Iterator.

This should be obvious, but it doesn't seem to be.

J. B. Rainsberger
  • 1,193
  • 9
  • 23
  • I think this is getting clearer as an acceptable approach with Java 8's streams, in particular suppliers and infinite streams. A usable example would be an infinite holiday generator, particularly considering movable holidays. – Jose Tepedino Nov 29 '19 at 14:43