Why Is Java's Iterator Not an Iterable

Why is Java's Iterator not an Iterable?

Because an iterator generally points to a single instance in a collection. Iterable implies that one may obtain an iterator from an object to traverse over its elements - and there's no need to iterate over a single instance, which is what an iterator represents.

Why does Java not allow foreach on iterators (only on iterables)?

So I have a somewhat reasonable explanation now:

Short version: Because the syntax also applies to arrays, which don't have iterators.

If the syntax were designed around Iterator as I proposed, it would be inconsistent with arrays. Let me give three variants:

A) as chosen by the Java developers:

Object[] array;
for(Object o : array) { }
Iterable<Object> list;
for(Object o : list) { }
Iterator<Object> iter;
while(iter.hasNext()) { Object o = iter.next(); }

The behaves the same way and is highly consistent across arrays and collections.
Iterators however have to use the classic iteration style (which at least is not likely to cause errors).

B) allow arrays and Iterators:

Object[] array;
for(Object o : array) { }
Iterable<Object> list;
for(Object o : list.iterator()) { }
Iterator<Object> iter;
for(Object o : iter) { }

Now arrays and collections are inconsistent; but arrays and ArrayList are very closely related and should behave the same way. Now if at any point, the language is extended to make e.g. arrays implement Iterable, it becomes inconsistent.

C) allow all three:

Object[] array;
for(Object o : array) { }
Iterable<Object> list;
for(Object o : list) { }
Iterator<Object> iter;
for(Object o : iter) { }

Now if we end up in unclear situations when either someone implements both Iterable and Iterator (is the for loop supposed to get a new iterator or iterate over the current - happens easily in tree-like structures!?!). A simple tie-braker ala "Iterable beats Iterator" unfortunately won't do: it suddenly introduces runtime vs. compile time difference and generics issues.

Now suddenly, we need to pay attention to whether we want to iterate over collections/iterables or arrays, at which point we have gained very little benefits at the cost of a big confusion.

The way "for each" is in Java (A) is very consistent, it causes very little programming errors, and it allows for the possible future change of turning arrays into regular objects.

There is a variant D) that would probably also work okay:
for-each for Iterators only. Preferrably by adding a .iterator() method to primitive arrays:

Object[] array;
for(Object o : array.iterator()) { }
Iterable<Object> list;
for(Object o : list.iterator()) { }
Iterator<Object> iter;
for(Object o : iter) { }

But this requires changes to the runtime environment, not just the compiler, and breaks backwards compatibility. Plus, the mentioned confusion is still present that

Iterator<Object> iter;
for(Object o : iter) { }
for(Object o : iter) { }

Only iterates over the data once.

What is the difference between iterator and iterable and how to use them?

An Iterable is a simple representation of a series of elements that can be iterated over. It does not have any iteration state such as a "current element". Instead, it has one method that produces an Iterator.

An Iterator is the object with iteration state. It lets you check if it has more elements using hasNext() and move to the next element (if any) using next().

Typically, an Iterable should be able to produce any number of valid Iterators.

Java: why can't iterate over an iterator?

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.

Why aren't Enumerations Iterable?

Enumeration hasn't been modified to support Iterable because it's an interface not a concrete class (like Vector, which was modifed to support the Collections interface).

If Enumeration was changed to support Iterable it would break a bunch of people's code.

Why does StreamT not implement IterableT?

People have already asked the same on the mailing list ☺. The main reason is Iterable also has a re-iterable semantic, while Stream is not.

I think the main reason is that Iterable implies reusability, whereas Stream is something that can only be used once — more like an Iterator.

If Stream extended Iterable then existing code might be surprised when it receives an Iterable that throws an Exception the
second time they do for (element : iterable).

Iterable and Iterator in java

You are right stating what these interfaces are used for. Indeed Iterable declares that the objects of a class implementing it may be iterated over, by providjng an Iterator specific to these objects. Having them is necessary because how exactly the object should be iterated depends on its internal implementation, and an Iterator is therefore specific to a given "collection" class.

Having said that, it is worth noting that although these interfaces are formally a part of Java Collections framework, they can be applied to other cases. Given an imaginary API to read CSV files for example, one can declare a CsvFile class to implement an Iterable<List<String>> and iterate over lines in a file with a dedicated Iterator<List<String>> which will read lines from the file one-by-one and split them into a List of Strings to return it from next().

Another important purpose of these interfaces is a language feature known as "for each" loop - only objects of a class implementing Iterable can be iterated with it. So, given an example from above about CsvFile API, it will also enable something like:

CsvFile csvFile = new CsvFile(pathToCsvFile);
for (List<String> record : csvFile) {
doSomethingWithIt(record);
}

As "for each" loop is purely a language feature, compiler will expand it to use an Iterator as usual.

P.S. Just because it hurts my eyes, I'd like to add that in the example above I would also suggest implementing an AutoCloseable for the CsvFile and using it with try-with-resources.

Why is it not possible for looping a class that directly implements Iterator?

Even though this kind of design is nothing but ridiculous, and just to entertain your question, you can write this:

public class Container implements Iterable<String>, Iterator<String> {
@Override public Iterator<String> iterator() { return this; }

... everything else you envision for your class ...
}

Then you'll be able to use

for (String s : container)

You may write this as a learning experience, but I strongly advise you never to try it on a real project. The main reason against it is the expectation on the iterator method to always return a fresh, independent iterator, while this implementation achieves the opposite. To give a specific example, imagine someone needing to iterate over the Cartesian product of your container with itself:

for (String s1 : container)
for (String s2 : container)
processPair(s1, s2);

Every Java programmer will tell you that, beyond any doubt, processPair will be called with each possible pair from the container. The behavior they would instead see from your class would make their heads spin. If a bug in production was ever traced to this implementation, your teammates would... let's just say they wouldn't appreciate it.



Related Topics



Leave a reply



Submit