38

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.

3
  • 1
    @Naman Related, but that appears to be the reverse of what Basil is doing in this Q&A. Commented Jan 23, 2020 at 1:44
  • 3
    Another way to get an Iterable would just be to collect to a List. Commented Jan 23, 2020 at 9:44
  • 2
    This question is different from the "duplicate". I was looking for THIS question, not the other. Thanks @Holger, for the answer. Commented Sep 15, 2021 at 9:15

2 Answers 2

55

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)); 
Sign up to request clarification or add additional context in comments.

7 Comments

Excellent explanation. It might be worth adding (as a slightly more advanced addendum) that while it is possible to say Iterable<String> 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.
@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
@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.
@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.
If you had a Supplier<Stream<T>> then you could create an Iterable<T> from that, but I think the use would be niche.
|
17

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.

9 Comments

Technically, you aren't casting from Stream<String> because stream::iterator isn't of type Stream<String>.
I'm not sure casting is the right terminology. You're creating an instance of Iterable through a method reference; the (Iterable<String>) just tells the compiler which functional interface is the target.
@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.
Iterable is effectively a functional interface, you're creating an Iterable with the iterator method overridden by stream.iterator. It would effectively be (Iterable<String>)()->stream.iterator() or even more explicitly new Iterable<String>(){ public Iterator<String> iterator(){ return stream.iterator();}. So you are not casting a Stream to Iterable, which would fail.
Doesn't work. (Iterator<String>)"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.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.