In the previous post we saw how lazy evaluation may make the code expressive without losing on performance. Here we'll see yet another benefit of lazy evaluations—Infinite Streams.
Streams may be unbounded or unrestricted in size. Let's look at a snippet of code first:
Stream.iterate(1, e -> e + 1)
We're calling the
iterate() method of the
java.util.stream.Stream interface, passing a value
1 and a lambda expression as arguments. This code snippet creates an infinite stream—it starts with the value
1, and may further yield a series of values
2, 3, 4, 5, 6, 7,...
The stream is infinite in length. "Infinite?" you say, "then where would you store it?" On the cloud of course.
It would not make sense to eagerly create an infinite stream, after all. We would run out of time, patience, memory,... and much more. The infinite streams simply say "I can produce an infinite amount of data and I dare you to ask for all of them."
Infinite stream are highly practical—they rely on the fact that we will only ask for finite amount of data and they produce the data just-in-time, on demand, lazily.
Let's expand the code snippet just a bit:
System.out.println( Stream.iterate(1, e -> e + 1) .filter(e -> e > 1000) .findFirst() .orElse(0));
This code gets an infinite stream that yields values starting with
1 and filters out any values from the stream that are less than or equal to
1000. The result of the
filter operation is another infinite stream. Remember from the previous post that the
filter method is an intermediate operation and it will not evaluate right away. In this example, the evaluation of both the lambda expression presented to
iterate and the one passed to
filter are lazy. It's the call to the terminal function
findFirst that triggers the computation of both of these lambda expressions, but only just enough times for the terminal function to be satisfied.
The result of this code is a value of
Infinite streams will not be possible without the capability to perform lazy evaluations. It is our responsibility, however, to make sure that the number of evaluations are actually finite. For example, while it makes sense to query a bounded or finite stream for its size—using the
count method—it would not make sense to call that method on an infinite stream. For example:
System.out.println(Arrays.asList(1, 2, 3).stream().count()); // prints 3 System.out.println(Stream.iterate(1, e -> e + 1).count()); // ...eternity
We took a peek at what makes infinite streams possible. In the next post we will look at an example of how infinite streams can make code highly expressive and remove some accidental complexities.