312

In Java 8 we have the class Stream<T>, which curiously have a method

Iterator<T> iterator()

So you would expect it to implement interface Iterable<T>, which requires exactly this method, but that's not the case.

When I want to iterate over a Stream using a foreach loop, I have to do something like

public static Iterable<T> getIterable(Stream<T> s) {
    return new Iterable<T> {
        @Override
        public Iterator<T> iterator() {
            return s.iterator();
        }
    };
}

for (T element : getIterable(s)) { ... }

Am I missing something here?

13
  • 8
    not to mention that the other 2 methods of iterable (forEach and spliterator) are also in Stream Commented Nov 21, 2013 at 19:09
  • 1
    this is needed to pass Stream to legacy APIs that expects Iterable Commented Nov 21, 2013 at 19:52
  • 14
    A good IDE (e.g. IntelliJ) will prompt you to simplify your code in getIterable() to return s::iterator; Commented Nov 21, 2013 at 19:54
  • 25
    You don't need a method at all. Where you have a Stream, and want an Iterable, just pass stream::iterator (or, if you prefer, () -> stream.iterator()), and you're done. Commented Apr 20, 2014 at 17:34
  • 8
    Unfortunately I cannot write for (T element : stream::iterator), so I'd still prefer if Stream would also implement Iterable or a method toIterable(). Commented May 15, 2015 at 5:34

9 Answers 9

234

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).

Sign up to request clarification or add additional context in comments.

12 Comments

Curiously there were already some iterables with this behavior in Java 7, e.g. DirectoryStream: While DirectoryStream extends Iterable, it is not a general-purpose Iterable as it supports only a single Iterator; invoking the iterator method to obtain a second or subsequent iterator throws IllegalStateException. (openjdk.java.net/projects/nio/javadoc/java/nio/file/…)
Unfortunately there's nothing is the documentation of Iterable about whether iterator should always or might not be callable multiple times. That's something they should put in there. This seems to be more of a standard practice than a formal specification.
If they're going to use excuses, you'd think they could at least add an asIterable() method - or overloads for all those methods which only take Iterable.
Maybe the best solution would have been making Java's foreach accept an Iterable<T> as well as potentially a Stream<T>?
JDK-8148917 contains a proposal to make streams IterableOnce which might solve this in a future Java release.
|
179

To convert a Stream to an Iterable, you can do

Stream<X> stream = null;
Iterable<X> iterable = stream::iterator

To pass a Stream to a method that expects Iterable,

void foo(Iterable<X> iterable)

simply

foo(stream::iterator) 

however it probably looks funny; it might be better to be a little bit more explicit

foo( (Iterable<X>)stream::iterator );

20 Comments

Image
You can also use this in a loop for(X x : (Iterable<X>)stream::iterator), although it looks ugly. Really, the whole situation is just absurd.
@HRJ IntStream.range(0,N).forEach(System.out::println)
I don't understand the double colon syntax in this context. What's the difference between stream::iterator and stream.iterator(), that makes the former acceptable for an Iterable but not the latter?
Answering myself: Iterable is a functional interface, so passing a function that implements it is enough.
I have to agree with @AleksandrDubinsky , The real problem is that there is a symantic workaround for(X x : (Iterable<X>)stream::iterator), which just allows the same operation albeit at a great code-noise. But then this is Java and we have tolerated the List<String> list = new ArrayList(Arrays.asList(array)) for a long time :) Although the powers that be could just offer us all List<String> list = array.toArrayList(). But the real absurdity, is that by encouraging everyone to use forEach on Stream, the exceptions must necessarily be unchecked [rant ended].
|
12

You can use a Stream in a for loop as follows:

Stream<T> stream = ...;

for (T x : (Iterable<T>) stream::iterator) {
    ...
}

(Run this snippet here)

(This uses a Java 8 functional interface cast.)

(This is covered in some of the comments above (e.g. Aleksandr Dubinsky), but I wanted to pull it out into an answer to make it more visible.)

1 Comment

Almost everyone needs to look at it twice or thrice before they realize how it even compiles. It is absurd. (This is covered in the response to the comments in the same comment stream, but I wanted to add the comment here to make it more visible.)
11

I would like to point out that StreamEx does implement Iterable (and Stream), as well as a host of other immensely awesome functionality missing from Stream.

Comments

8

kennytm described why it's unsafe to treat a Stream as an Iterable, and Zhong Yu offered a workaround that permits using a Stream as in Iterable, albeit in an unsafe manner. It's possible to get the best of both worlds: a reusable Iterable from a Stream that meets all the guarantees made by the Iterable specification.

Note: SomeType is not a type parameter here--you need to replace it with a proper type (e.g., String) or resort to reflection

Stream<SomeType> stream = ...;
Iterable<SomeType> iterable = stream.collect(toList()):

There is one major disadvantage:

The benefits of lazy iteration will be lost. If you planned to immediately iterate over all values in the current thread, any overhead will be negligible. However, if you planned to iterate only partially or in a different thread, this immediate and complete iteration could have unintended consequences.

The big advantage, of course, is that you can reuse the Iterable, whereas (Iterable<SomeType>) stream::iterator would only permit a single use. If the receiving code will be iterating over the collection multiple times, this is not only necessary, but likely beneficial to performance.

8 Comments

Have you tried to compile your code before answering? It doesn't work.
@TagirValeev Yes, I did. You need to replace T with the appropriate type. I copied the example from working code.
@TagirValeev I tested it again in IntelliJ. It appears that IntelliJ sometimes gets confused by this syntax; I haven't really found a pattern to it. However, the code compiles fine, and IntelliJ removes the error notice after compiling. I guess it's just a bug.
Stream.toArray() returns an array, not an Iterable, so this code still doesn't compile. but it may be a bug in eclipse, since IntelliJ seems to compile it
@Zenexer How were you able to assign an array to an Iterable?
|
3

Stream does not implement Iterable. The general understanding of Iterable is anything that can be iterated upon, often again and again. Stream may not be replayable.

The only workaround that I can think of, where an iterable based on a stream is replayable too, is to re-create the stream. I am using a Supplier below to create a new instance of stream, everytime a new iterator is created.

    Supplier<Stream<Integer>> streamSupplier = () -> Stream.of(10);
    Iterable<Integer> iterable = () -> streamSupplier.get().iterator();
    for(int i : iterable) {
        System.out.println(i);
    }
    // Can iterate again
    for(int i : iterable) {
        System.out.println(i);
    }

Comments

2

If you don't mind using third party libraries cyclops-react defines a Stream that implements both Stream and Iterable and is replayable too (solving the problem kennytm described).

 Stream<String> stream = ReactiveSeq.of("hello","world")
                                    .map(s->"prefix-"+s);

or :-

 Iterable<String> stream = ReactiveSeq.of("hello","world")
                                      .map(s->"prefix-"+s);

 stream.forEach(System.out::println);
 stream.forEach(System.out::println);

[Disclosure I am the lead developer of cyclops-react]

Comments

0

Not perfect, but will work:

iterable = stream.collect(Collectors.toList());

Not perfect because it will fetch all items from the stream and put them into that List, which is not exactly what Iterable and Stream are about. They are supposed to be lazy.

Comments

-1

You can iterate over all files in a folder using Stream<Path> like this:

Path path = Paths.get("...");
Stream<Path> files = Files.list(path);

for (Iterator<Path> it = files.iterator(); it.hasNext(); )
{
    Object file = it.next();

    // ...
}

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.