6

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?

3
  • 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, 2017 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, 2017 at 17:20
  • possible duplicate of stackoverflow.com/q/20129762
    – wlnirvana
    Jan 24, 2021 at 2:35

1 Answer 1

11

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));
        }
    }
}
11
  • 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, 2017 at 19:17
  • 1
    @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, 2017 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, 2017 at 21:14
  • 2
    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, 2017 at 21:51
  • 1
    It doesn't explain why for-each doesn't work on streams or iterators though.
    – user253751
    Nov 24, 2020 at 19:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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