Idiomatic way to use for-each loop given an iterator?

Mark Peters picture Mark Peters · Oct 7, 2010 · Viewed 10.1k times · Source

When the enhanced for loop (foreach loop) was added to Java, it was made to work with a target of either an array or Iterable.

for ( T item : /*T[] or Iterable<? extends T>*/ ) {
    //use item
}

That works great for Collection classes that only implement one type of iteration, and thus have a single iterator() method.

But I find myself incredibly frustrated the odd time I want to use a non-standard iterator from a Collection class. For example, I was recently trying to help somebody use a Deque as a LIFO/stack but then print the elements in FIFO order. I was forced to do this:

for (Iterator<T> it = myDeque.descendingIterator(); it.hasNext(); ) {
   T item = it.next();
   //use item
}

I lose the advantages of the for-each loop. It's not just about keystrokes. I don't like exposing the iterator if I don't have to, since it's easy to make the mistake of calling it.next() twice, etc.

Now ideally I think the for-each loop should have accepted an Iterator as well. But it doesn't. So is there an idiomatic way of using the for-each loop in these circumstances? I'd also love to hear suggestions that use common collections libraries like Guava.

The best I can come up with in absense of a helper method/class is:

for ( T item : new Iterable<T>() { public Iterator<T> iterator() { return myDeque.descendingIterator(); } } ) {
    //use item
}

Which isn't worth using.

I'd love to see Guava have something like Iterables.wrap to make this idiomatic, but didn't find anything like that. Obviously I could roll my own Iterator wrapper via a class or helper method. Any other ideas?

Edit: As a side-note, can anybody give a valid reason for why the enhanced for-loop shouldn't have been able to just accept an Iterator? It would probably go a long way to making me live with the current design.

Answer

Mark Peters picture Mark Peters · Oct 7, 2010

Why doesn't the enhanced for loop just accept an iterator?

I want to gather a few of the potential reasons from the various answers as to why the for-each loop doesn't simply accept an iterator.

  1. Convenience: The for-each loop was created partly for convenience for the common operation of performing an action given each element of a collection. It has no obligation or intention of replacing the explicit use of iterators (obviously if you want to remove elements, you need an explicit reference to the iterator).
  2. Readability: The for-each loop for ( Row r : table ) is meant to be extremely readable as "for each row "r" in table...". Seeing for ( Row r : table.backwardsIterator() ) breaks that readability.
  3. Transparency: If an object is both an Iterable and an Iterator, what will be the behaviour? Though it's easy to make a consistent rule (e.g. Iterable before Iterator) the behaviour will be less transparent to developers. Furthermore, this will have to be checked at compile time.
  4. Encapsulation/Scope: This is (in my opinion) the most important reason. The for-each loop is designed to encapsulate the Iterator and limits its scope to the loop. This makes the loop "read-only" in two ways: it doesn't expose the iterator, meaning there is nothing (easily) tangible that has its state altered by the loop, nor can you alter the state of the operand in the loop (as you can by interfacing directly with an Iterator via remove()). Passing the Iterator yourself necessarily means the Iterator is exposed, making you lose both of those "read-only" attributes of the loop.