5

The other day I was playing around with an experiment and I had a for loop something like so:

for (Node node : children) {
  // do stuff with node ...
}

And then I changed it to do this:

for (Node node : children.stream().filter(n -> n.isCreated())) {
  // do stuff with node ...
}

And got this error: "Can only iterate over an array or an instance of java.lang.Iterable" This reopens some old wounds for me. Here's what I find silly here. Stream (the class that doesn't implement Iterable) has an iterator() method. Am I alone in thinking this is a major oversight? Is there any logical reason why it shouldn't be declared implement Iterable? I get that functional programming is cool and all but it can't be that the reason is to try to encourage people switch all their for loops to forEach() calls.

So the second part of this annoyance is that even if you do this:

for (Node node : children.stream().filter(n -> n.isCreated()).iterator()) {
  // do stuff with node ...
}

You get the exact same error. This is an old annoyance for me because when they introduced the for(:) syntax, it never made sense that Iterator wasn't one of the things you could use along with Iterable and arrays. Now, it seems even more obnoxious given that all that would need to be done to make Iterator implement Iterable is add the following method to Iterator:

default Iterator<E> iterator() {
  return this;
}

It's easy enough to work around these issues with methods like this:

static <T> Iterable<T> iterable(final Stream<T> stream)
{
  return new Iterable<T>() {
    @Override
    public Iterator<T> iterator() {
      return stream.iterator();
    }
  };
}

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

Or with lambas:

Iterable<T> foo = () -> iterator;

But why? It just seems silly to have to do this. Is anyone aware of why this wasn't done or come up with any legitimate reasons for not making these two interfaces Iterable?

  • 2
    I don't know about Iterator, but a Stream is potentially infinite, so a naive iteration could run forever. Not implementing the Iterable interface prevents these kinds of errors. – hoffmale Jun 6 '17 at 17:13
  • @hoffmale I'm not following. Stream already declares an iterator method (via BaseStream.) How would declaring it to implement the Iterable interface effect what you are referring to? – JimmyJames Jun 6 '17 at 17:20
7

They would break the Liskov Substitution Principle implementing Iterable<>.

An important part of the Iterable<> contract that isn't describable in Java is that you can iterate multiple times. Neither Iterator<> nor Stream<> have similar restrictions, so perfectly acceptable Iterator<>s or Streams<> that generate values only once, when converted to Iterable<> by your proposed conversion, will break code that expects to be able to independently operate on the values multiple times.

E.g.

<T> Iterable<Pair<T, T>> crossProduct(Iterable<T> source)
{
    List<Pair<T, T>> result = new ArrayList<Pair<T, T>>();

    for (outer : source) {
        for (inner : source) {
            result.add(new Pair<T, T>(outer, inner));
        }
    }
}
| improve this answer | |
  • 1
    Definitely a valid point you make. I would probably accept this if you can conjure up some evidence that this is part of the contract. The javadoc comment for Iterable says: "Implementing this interface allows an object to be the target of the "for-each loop" statement. See For-each Loop" and that's it. What you say makes sense in practice except for the fact that I don't see how iterating multiple times is a requirement of the enhanced for loop. So if you are right, then it seems like a mistake to use it for the for-each loop. – JimmyJames Jun 6 '17 at 19:17
  • @JimmyJames - I'd say it's implicit in the description of the iterator method ("Returns an iterator over a set of elements of type T. Returns: an Iterator."). Note that it doesn't allow the implementation to restrict the number of times it is called. Yes, I agree that it is a mistake that for-each loops can't accept Iterators (although i think it would be a bad idea to allow them to do so and not provide a warning on any use of the iterator afterwards), but to make a once-only Iterable is definitely dubious, if not outright wrong. – Jules Jun 6 '17 at 21:11
  • (Which isn't to say that I don't have an IterableAdapter class in my standard utility library... I just feel dirty every time I use it) – Jules Jun 6 '17 at 21:14
  • @Jules Where did you get that description for the iterable() method? The one for JDK 8 says: "Returns an iterator over elements of type T." I'm pretty sure 'set' would be incorrect here. On the other hand BaseStream says: "Returns an iterator for the elements of this stream." which is hardly different aside from 'of this stream'. It also says "This is a terminal operation." which actually calls out that the iterator consumes the stream. But I'm still not seeing anything that says Iterable is implicitly reusable. Is it assumed because it was retrofitted onto Collection? – JimmyJames Jun 6 '17 at 21:33
  • 1
    There is a notable absence of language to the effect of "An Iterable should be operated on ... only once", as compared to Stream, which supports an "Iterable is minimal Collection" theory. Another point is the addition of Iterator::forEachRemaining in Java 8 – Caleth Jun 6 '17 at 21:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.