35

Stream inherits an iterator() method to produce an Iterator.

But I need an Iterable rather than an Iterator.

For example, given this string:

String input = "this\n" +
        "that\n" +
        "the_other";

…I need to pass those parts of string as an Iterable to a specific library. Calling input.lines() yields a Stream. So I would be set if I could make that Stream into a Iterable of its elements.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154

2 Answers2

43

As explained in Why does Stream<T> not implement Iterable<T>?, an Iterable bears the expectation to be able to provide an Iterator more than once, which a Stream can’t fulfill. So while you can create an Iterable out of a Stream for an ad-hoc use, you have to be careful about whether attempts to iterate it multiple times could exist.

Since you said, “I need to pass those parts of string as an Iterable to a specific library”, there is no general solution as the code using the Iterable is outside your control.

But if you are the one who creates the stream, it is possible to create a valid Iterable which will simply repeat the stream construction every time an Iterator is requested:

Iterable<String> lines = () -> "this\nthat\nthe_other".lines().iterator();

This fulfills the expectation of supporting an arbitrary number of iterations, while not consuming more resources than a single stream when being traversed only once.

for(var s: lines) System.out.println(s);
lines.forEach(System.out::println);
System.out.println(String.join("\n", lines));
Holger
  • 285,553
  • 42
  • 434
  • 765
  • 2
    Excellent explanation. It might be worth adding (as a slightly more advanced addendum) that while it is possible to say `Iterable lines = "this\nthat\nthe_other".lines()::iterator;` this is one of those rare cases where a method reference is not the same as a lambda and in fact would have undesired effect. – Klitos Kyriacou Jan 23 '20 at 09:13
  • 4
    @KlitosKyriacou method references of the form `expression::name` are never the same as a lambda expressions, not even for `System.out::println`. But here, we have one of the cases where it matters – Holger Jan 23 '20 at 09:19
  • In this case, lines would be getting called every time, but I don't see the difference between between a method and a lambda if `Stream lines = str.lines();` then you create the `Iterable iter = lines::iterator` versus `()->lines.iterator()`. – matt Jan 23 '20 at 09:50
  • 2
    @matt nobody said that there was a difference between `stream::iterator` and `() -> stream.iterator()`. In fact, my answer precisely said, there there is *no general solution* for converting an existing stream to a valid iterable that allows multiple iterations. Repeating the stream construction every time an iterator is requested, i.e. here it means calling `lines()` every time, is what makes the difference. – Holger Jan 23 '20 at 09:55
  • I'm sorry, I meant in your comment where you said "expression::name" is never the same as a lambda expression. I think your answer is good. – matt Jan 23 '20 at 10:00
  • 5
    @matt well, formally, it isn’t the same. `expression::name` means “*evaluate `expression` once and capture the result*”, whereas `() -> expression.name(…)` means “*evaluate `expression` each time the function body is evaluated*”. The differences are tiny when “`expression`” is just a local variable, but even then, it’s different, i.e. `stream::iterator` will behave different than `() -> stream.iterator()` when `stream` is `null`. So my statement still holds, `expression::name` is always different to a lambda expression, the question is, how much does it matter for my specific use case. – Holger Jan 23 '20 at 10:04
  • 1
    If you had a `Supplier>` then you could create an `Iterable` from that, but I think the use would be niche. – Tom Hawtin - tackline Jan 23 '20 at 22:36
13

tl;dr

Just cast, no need to convert.

Cast Stream<String>::iterator to Iterable<String>.

Details

CAUTION See Answer by Holger explaining dangers of using a stream-backed Iterable.

Yes, you can make an Iterable from a Stream.

The solution is simple, but not obvious. See this post on Maurice Naftalin's Lambda FAQ.

The signature of the iterator method of BaseStream (superclass of Stream) returning a Iterator matches the only method of the functional interface Iterable, so the method reference Stream<T>::iterator can be used as an instance of Iterable<T>. (The fact that both methods have the same name is coincidental.)

Make your input.

String input = "this\n" +
        "that\n" +
        "the_other";
Stream<String> stream = input.lines() ;

Use the method reference to generate a Iterable<String>.

Iterable< String > iterable = stream::iterator;

Test the results.

for ( String s : iterable ) 
{
    System.out.println( "s = " + s );
}

See this code run live at IdeOne.com.

s = this

s = that

s = the_other

CAVEAT Beware of the risk of stream-backed Iterable. Explained in the correct Answer by Holger.

Willis Blackburn
  • 8,068
  • 19
  • 36
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • 7
    Technically, you aren't casting from `Stream` because `stream::iterator` isn't of type `Stream`. – kaya3 Jan 22 '20 at 23:36
  • 4
    I'm not sure casting is the right terminology. You're _creating_ an instance of `Iterable` through a method reference; the `(Iterable)` just tells the compiler which functional interface is the target. – Slaw Jan 23 '20 at 01:24
  • @Slaw & @kaya3 These subtleties have escaped my understanding. I do not see how `(Iterable)` is anything but a cast, yet I certainly do not understand the situation, as your comments prompted me to put an extra set of parens around `( (Iterable) stream )` which causes the code to fail – proving my understanding is flawed. So please do edit my Answer to clarify, or post your own Answer with a better explanation. – Basil Bourque Jan 23 '20 at 01:30
  • 4
    @Slaw the problem is, it is correct that there is a cast, but it is not correct to say that the cast is the solution. The solution consists of a method reference and a cast. The cast could be replaced by another construct providing the intended target type, e.g. passing the method reference to a method expecting an iterable or assigning it to a variable of type iterable. But the solution still requires a method reference or lambda expression for the conversion. So it’s nonsense to say “*just cast, rather than convert*” when there’s still a conversion (adapter code) *and* a cast. – Holger Jan 23 '20 at 08:15
  • 4
    Iterable is effectively a functional interface, you're creating an Iterable with the iterator method overridden by stream.iterator. It would effectively be `(Iterable)()->stream.iterator()` or even more explicitly `new Iterable(){ public Iterator iterator(){ return stream.iterator();}`. So you are not casting a Stream to Iterable, which would fail. – matt Jan 23 '20 at 08:18
  • @matt I am sorry that my example using a `for` loop confused matters. The goal of the Question is to get an `Iterable`. To that end, one does indeed cast without convert. My call to `Iterable::iterator` was a distraction, here just to demonstrate the results as output to the console. I have now broken up my code shown in this Answer to be more explicit about the solution being: cast stream to get an `Iterable`. I also added a caveat to point the reader to the very important Answer by Holger. – Basil Bourque May 08 '20 at 15:08
  • 3
    Doesn't work. `(Iterator)"one\ntwo".lines();` produces. `Exception in thread "main" java.lang.ClassCastException: class java.util.stream.ReferencePipeline$Head cannot be cast to class java.util.Iterator` thats from openjdk version "11.0.6" 2020-01-14. Your code compiles but throws an exception. I don't know what you're actually running. – matt May 08 '20 at 15:32
  • 2
    Your example is different that the code you pasted! You are **still using ::iterator** which is a method reference. The code you pasted with a literal cast is broken. – matt May 08 '20 at 15:34
  • Edited the answer to use `::iterator`. Just casting the `Stream` itself doesn't work. – Willis Blackburn Jun 15 '21 at 15:24