Creating Streams from Collections and Arrays
Stream.generate(supplier) creates a sequential stream that is effectively infinite: it keeps producing values forever or until producing one causes an error.
Streams That Keep Going
Most of the streams you have met so far have a clear beginning and a clear end. A stream built from a list stops when the list runs out of elements. This article looks at a different kind of stream, one that does not run out. Its values keep arriving for as long as the pipeline keeps asking for them.
As you read, keep two questions in mind: what is the source of the values, and what would tell the program to stop? The answers to both questions decide what the program prints and whether it finishes at all.
- Stream.generate takes a supplier; here the supplier is the lambda () -> counter[0]++
- counter is a one-element array, so the lambda can change its contents while the reference stays fixed
limit(5)is the stopping point that bounds the infinite streamforEachis the terminal operation that asks the pipeline for values
What do you think happens?
What does this program print, and how many lines does it produce?
Reveal answer
It prints 0, 1, 2, 3, and 4 on five separate lines, then the program finishes.
The post-increment returns the old value before adding one, so the first call returns 0 and the counter then holds 1. limit(5) allows exactly five supplier requests, so exactly five values are printed.
Tracing the Supplier Calls
The program above is built around one idea: the supplier does not run on its own schedule. It runs only when the terminal operation asks for a value. The diagram below follows the requests one at a time. Each request causes exactly one supplier call, and each call returns the next value in the sequence.
Read the diagram from left to right. limit does not generate values itself. It counts the requests coming through the pipeline, and once five have passed, it stops sending requests upstream. The supplier only ever runs in response to a request, so the five-request cutoff is what makes the program finish.
What Makes a Stream Effectively Infinite
Stream.generate(supplier) creates a sequential stream that is effectively infinite. The stream will continue to produce values forever, or until trying to do so produces an error. Nothing in the stream itself marks an end.
Each time the pipeline needs another element, the stream asks the supplier for one more value. The supplier is the only thing that determines what those values are. If the supplier returns the same value every time, the stream carries that value forever. If the supplier changes a value on each call, as the counter in the example does, the stream carries a changing sequence.
Because the stream is sequential, its elements are produced and consumed one at a time in encounter order. This is what makes the counter behave predictably: each supplier call happens in turn, so the values appear in the same order every time the program runs.
Three Forms of generate
The same idea appears in three forms. Stream.generate takes a supplier and produces a stream of whatever type that supplier returns. The two primitive variants are specialized for numbers, and they take their own supplier interfaces. The table summarizes the three methods.
| Method | Supplier it accepts | Stream it produces |
|---|---|---|
| Stream.generate(s) | a Supplier | a Stream of whatever type the supplier returns |
| IntStream.generate(s) | an IntSupplier | a stream of int values |
| DoubleStream.generate(s) | a DoubleSupplier | a stream of double values |
The three generate methods and the functional interface each one accepts
The primitive versions avoid boxing the values into objects. That is the reason to choose IntStream.generate over Stream.generate when you are producing numbers. The code below shows the primitive form with the same counter pattern.
0
1
2IntStream.generate accepts an IntSupplier, so the lambda returns an intlimit(3)bounds the stream after three values- Output shows three lines because three values were requested
Finding Where the Output Goes Wrong
When an infinite stream prints something unexpected, compare the printed values with your prediction in order. The first position where they differ identifies the supplier call that behaved differently. Because the stream is sequential, that first wrong value is the earliest point of failure, and every later value may simply be following from it.
Next, check the places where state can change: the supplier's body and any variable it reads. In the counter example, the post-increment inside the supplier is the only place the counter moves. Changing the operator changes the whole sequence, as the comparison below shows.
int[] counter = {0};
Stream.generate(() -> counter[0]++)
.limit(5)
.forEach(System.out::println);Post-increment returns the old value, so the first printed value is 0
int[] counter = {0};
Stream.generate(() -> ++counter[0])
.limit(5)
.forEach(System.out::println);Pre-increment returns the new value, so the first printed value is 1 and the sequence shifts by one
In the after version, the first printed line is 1 instead of 0. The divergence appears at the first printed line, not at the increment itself, which is why comparing outputs in order is the right first step.
Common mistake Expecting the stream to end on its own
What it looks likeStream.generate(() -> counter[0]++).forEach(System.out::println);Why it failsA generated stream has no built-in end, so the requests never stop.
Do this insteadAdd limit(n) before the terminal operation to bound the stream.
Common mistake Looking for the bug in the wrong line
What it looks likeChecking the forEach call when the printed values are already off by one
Why it failsThe stream is sequential, so the first wrong value is the earliest point of failure.
Do this insteadFind the first differing value in order, then inspect the supplier's body.
Common mistake Assuming the primitive variant needs a different kind of supplier logic
Why it failsIntStream.generate uses an IntSupplier andDoubleStream.generate uses a DoubleSupplier; the counter pattern is the same.Do this insteadKeep the same reasoning and check which supplier interface the method accepts.
Creating Streams from a Collection
A stream is a way of processing a sequence of values, but it needs a starting point that supplies those values. In this article the starting point is a collection such as a list or a set. Lists and sets both serve as stream sources in the same way, so you do not need a different technique for each type of collection. What changes between creation calls is the kind of stream that comes out, not the kind of collection.
Given a collection named c, calling c.stream() produces a sequential stream holding the same values as the collection. Calling c.parallelStream() produces a parallel stream from the same collection. A second route also reaches a parallel stream: you create a sequential stream with stream() and then call parallel() on it. The result is a parallel stream, so this route reaches the same destination as parallelStream() with one extra step.
false
true
truestream()gives a sequential stream, soisParallel()reports falseparallelStream()gives a parallel stream directlystream()followed byparallel()also gives a parallel stream
The method name tells you which kind of stream you are asking for: stream() for sequential, parallelStream() for parallel. Keeping the names straight is the core skill here. The source also notes that a sequential stream produces its elements in encounter order, which is why sequential results are predictable from run to run.
Predicting the Kind of Stream
Choosing the creation call for a parallel stream
A program holds a List<String> named words and needs a parallel stream. Three calls are proposed: words.stream(), words.parallelStream(), and words.stream().parallel(). Which of them produce a parallel stream?
- 1Read the first call
words.stream()calls the method whose name indicates a sequential stream, so it is not parallel. - 2Read the second call
words.parallelStream()calls the method whose name indicates a parallel stream directly, so it is parallel. - 3Read the third call
words.stream().parallel() starts sequential and then callsparallel(), which yields a parallel stream. - 4Check against the expected result
Two of the three calls produce parallel streams, and the first is the only sequential one.
words.stream() is sequential. words.parallelStream() and words.stream().parallel() both produce parallel streams.
Practice: Predicting and Verifying
A program runs Stream.generate(() -> ++counter[0]).limit(4).forEach(System.out::println) with counter initialized to {0}. Write down the four printed values before checking. Then identify the first printed line where your prediction could differ, and explain which part of the supplier causes that difference.
Hints
- Pre-increment returns the new value, so consider what the first call returns.
- Compare your four predicted values with the printed values in order, starting from the first line.
- The supplier's body is the only place the counter changes.
Key Takeaways
Stream.generate(supplier)creates a sequential, effectively infinite stream whose values come only from the supplier, so a stopping point such aslimit()is required before a terminal operation.- The supplier runs only when the pipeline requests a value, so
limit()decides how many supplier calls happen. - Stream.generate,
IntStream.generate, andDoubleStream.generate accept a Supplier, IntSupplier, and DoubleSupplier respectively; the primitive versions avoid boxing. - To find an output divergence, compare printed values in order, find the first difference, and then inspect the supplier.
collection.stream()andcollection.parallelStream()create sequential and parallel streams from the same collection, andstream().parallel() reaches a parallel stream in two steps.