22

I read Why is Java's Iterator not an Iterable? and Why aren't Enumerations Iterable?, but I still don't understand why this:

void foo(Iterator<X> it) {
  for (X x : it) {
    bar(x);
    baz(x);
  }
}

was not made possible. In other words, unless I'm missing something, the above could have been nice and valid syntactic sugar for:

void foo(Iterator<X> it) {
  for (X x; it.hasNext();) {
    x = it.next();
    bar(x);
    baz(x);
  }
}
Community
  • 1
  • 1
noamtm
  • 12,435
  • 15
  • 71
  • 107

6 Answers6

19

Most likely the reason for this is because iterators are not reusable; you need to get a fresh Iterator from the Iterable collection each time you want to iterate over the elements. However, as a quick fix:

private static <T> Iterable<T> iterable(final Iterator<T> it){
     return new Iterable<T>(){ public Iterator<T> iterator(){ return it; } };
}

//....
{
     // ...
     // Now we can use:
     for ( X x : iterable(it) ){
        // do something with x
     }
     // ...
}
//....

That said, the best thing to do is simply pass around the Iterable<T> interface instead of Iterator<T>

Colonel Thirty Two
  • 23,953
  • 8
  • 45
  • 85
Michael Aaron Safyan
  • 93,612
  • 16
  • 138
  • 200
  • +1: with arrays and `Iterable` you can write the same loop twice in a row and have it work as expected. That doesn't work with `Iterator`. – Joachim Sauer Apr 08 '10 at 07:44
  • You're right it would be best, but I'm using my own iterator over something that's not a real Collection (it's a DOM NodeList). Otherwise, your answer makes sense, thanks. – noamtm Apr 08 '10 at 07:46
  • 5
    @noamtm: if it's not a `Collection` but can provide an `Iterator`, then it should probably implement `Iterable`. – Joachim Sauer Apr 08 '10 at 09:37
  • 1
    Joachim: there may be more than one way to iterate over something (e.g. `Iterator iterateInNaturalOrder()`, `Iterator iterateInSortedOrder()`, `Iterator iterateOverValidValues()`), in which case Iterable is *not* appropriate, since it provides only one way to iterate over a collection, namely `iterator()`. – Jason S May 05 '11 at 19:32
  • @JasonS an Iterable can still be used in those cases as the return type for those methods (though clearly a single Iterable would not suffice). – Michael Aaron Safyan Jul 29 '15 at 05:29
  • Java 8 syntatic sugar allows to [make this solution much shorter](http://stackoverflow.com/a/31704850/603516). – Vadzim Jul 29 '15 at 15:32
  • @Vadzim that's still the same solution, except you've made the "iterable" helper function into an anonymous lambda. – Michael Aaron Safyan Jul 30 '15 at 01:33
  • One could even add a couple of lines of code to the returned `Iterable` instance to ensure that its `iterator()` is called only once. Just in case. – Just a student Aug 11 '18 at 13:17
10

but I still don't understand why this [...] was not made possible.

I can see several reasons:

  1. Iterators are not reusable, so a for/each would consume the iterator - not incorrect behavior, perhaps, but unintuitive to those who don't know how the for/each is desugared.
  2. Iterators don't appear "naked" in code all that often so it would be complicating the JLS with little gain (the for/each construct is bad enough as it is, working on both Iterables and arrays).
  3. There's an easy workaround. It may seem a little wasteful to allocate a new object just for this, but allocation is cheap as it is and escape analysis would rid you even of that small cost in most cases. (Why they didn't include this workaround in an Iterables utility class, analogous to Collections and Arrays, is beyond me, though.)
  4. (Probably not true - see the comments.) I seem to recall that the JLS can only reference things in java.lang[citation needed], so they'd have to create an Iterator interface in java.lang which java.util.Iterator extends without adding anything to. Now we have two functionally equivalent iterator interfaces. 50% of the new code using naked iterators will choose the java.lang version, the rest use the one in java.util. Chaos ensues, compatibility problems abound, etc.

I think points 1-3 are very much in line with how the Java language design philosophy seems to go: Don't surprise newcomers, don't complicate the spec if it doesn't have a clear gain that overshadows the costs, and don't do with a language feature what can be done with a library.

The same arguments would explain why java.util.Enumeration isn't Iterable, too.

Community
  • 1
  • 1
gustafc
  • 28,465
  • 7
  • 73
  • 99
  • "I seem to recall that the JLS can only reference things in java.lang" Ignoring any 'discussion' or 'example' contexts (of which there are many), the JLS refers several times to java.io.Serializable (§4.10.3 and others) as a special case (e.g. Serializable is a valid supertype of a primitive array). Irrelevant to the discussion at hand, but interesting anyway. – Cowan Apr 08 '10 at 10:48
  • There is no `java.util.Enumerator`. Did you mean `Enumeration`? – Joachim Sauer Apr 08 '10 at 11:12
  • @Cowan, thanks. I even looked at the JLS but managed to miss those parts. @Joachim, I did. Updated my post. – gustafc Apr 08 '10 at 11:16
7

The for(Type t : iterable) syntax is only valid for classes that implement Iterable<Type>.

An iterator does not implement iterable.

You can iterate over things like Collection<T>, List<T>, or Set<T> because they implement Iterable.

The following code is equivalent:

for (Type t: list) {
    // do something with t
}

and

Iterator<Type> iter = list.iterator();
while (iter.hasNext()) {
    t = iter.next();
    // do something with t
}

The reason this was not made possible, is because the for-each syntax was added to the language to abstract out the Iterator. Making the for-each loop work with iterators would not accomplish what the for-each loop was created for.

davenpcj
  • 12,508
  • 5
  • 40
  • 37
jjnguy
  • 136,852
  • 53
  • 295
  • 323
  • 3
    That's obvious, it doesn't answer my question of "why it wasn't made possible". – noamtm Apr 08 '10 at 07:41
  • 1
    A `Map` (which would be `Map`, btw) does *not* implement `Iterable`. You'd have to use `keySet()`, `values()` or `entrySet()` and iterate over those. – Joachim Sauer Apr 08 '10 at 07:41
  • @Joach My bad, I was thinking of C#. – jjnguy Apr 08 '10 at 07:44
  • Interesting, so an `IDictionary` (which I assume is the .NET equivalent of a `Map`) provides an `IEnumerator`. What does that enumerate over? Does it return entries? values? keys? I can't seem to get that from the documentation: http://msdn.microsoft.com/en-us/library/system.collections.idictionaryenumerator_members(v=VS.90).aspx – Joachim Sauer Apr 08 '10 at 08:51
  • 1
    @joach It iterates over `Entry`. So you get one entry per iteration. It is basically a key, value pair. I really like it. – jjnguy Apr 08 '10 at 08:56
6

Actually, you can.

There is very short workaround available on java 8:

for (X item : (Iterable<X>) () -> iterator)

See How to iterate with foreach loop over java 8 stream for the detailed explanation of the trick.

And some explanations why this was not natively supported can be found in related question:

Why does Stream<T> not implement Iterable<T>?

Community
  • 1
  • 1
Vadzim
  • 24,954
  • 11
  • 143
  • 151
  • Thanks, but does not really answer my question. The question (from 2010) was really about the design choice (hence "why", not "how"). +1 because it's nice to know, though. – noamtm Jul 30 '15 at 07:11
2

Iterators are not meant be reused (i.e.: used in more than one iteration loop). In particular, Iterator.hasNext() guarantees that you can safely call Iterator.next() and indeed get the next value from the underlying collection.

When the same iterator is used in two concurrently running iterations (let's assume a multi-threading scenario), this promise can no longer be kept:

while(iter.hasNext() {
   // Now a context switch happens, another thread is performing
   //    iter.hasNext(); x = iter.next();

  String s = iter.next();  
          // A runtime exception is thrown because the iterator was 
          // exhausted by the other thread
}

Such scenarios completely break the protocol offered by Iterator. Actually, they can occur even in a single threaded program: an iteration loop calls another method which uses the same iterator to perform its own iteration. When this method returns, the caller is issuing an Iterator.next() call which, again, fails.

Eric
  • 362
  • 1
  • 10
Itay Maman
  • 30,277
  • 10
  • 88
  • 118
0

Because the for-each is designed to read as something like:

for each element of [some collection of elements]

An Iterator is not [some collection of elements]. An array and an Iterable is.

polygenelubricants
  • 376,812
  • 128
  • 561
  • 623