I was going through a Streams Video when I found myself confused as to how Streams API was giving lazy evaluation over the imperative for loop approach.
Here's the typical for loop code which checks for the first number that is greater than three and even, it then simply prints it and returns.
List<Integer> arr = Arrays.asList(1, 2, 3, 5, 4, 6, 7, 8, 9);
for (int i : arr) {
System.out.println(" Checking if is Greater: " + i);
if (i > 3) {
System.out.println("checking if is Even " + i);
if (i % 2 == 0) {
System.out.println(i * 2);
break;
}
}
}
Here the expected output:
Checking if is Greater: 1
Checking if is Greater: 2
Checking if is Greater: 3
Checking if is Greater: 5
Checking if is Even 5
Checking if is Greater: 4
Checking if is Even 4
8
Now here's the same code using Streams API:
arr.stream()
.filter(Lazy::isGreater)
.filter(Lazy::isEven)
.map(Lazy::doubleIt)
.findFirst();
It too evaluates the same way. So how is filter()
providing something different which we can't get using the traditional for loops?
Lazy evaluation is another functional programming technique that is not new to Java 8. This technique delays the evaluation of an expression until its value is needed. In most cases, Java eagerly evaluates an expression that is bound to a variable.
The possibility of working on the infinite sequence of elements is predicated on the fact that streams are built to be lazy. This laziness is achieved by a separation between two types of the operations that could be executed on streams: intermediate and terminal operations.
Streams are lazy because intermediate operations are not evaluated unless terminal operation is invoked. Each intermediate operation creates a new stream, stores the provided operation/function and return the new stream.
Lazy evaluation is an evaluation strategy which delays the evaluation of an expression until its value is needed. The opposite of this is eager evaluation, where an expression is evaluated as soon as it is bound to a variable.[wikipedia]
Here's the key: composability
arr.stream()
.filter(Lazy::isGreater)
.filter(Lazy::isEven)
.map(Lazy::doubleIt)
.findFirst();
That seems innocuous, but this is a value now:
arr.stream()
.filter(Lazy::isGreater)
You can hand it to a method and build on it.
What can do with an equivalent for loop? You can copy-paste it wherever you use it. It's not a composable abstraction.
Moreover, the Stream makes the how of iterating and processing the data abstract as well. It could with worker pools, or fork join, or it could go in an order favorable to CPU cache locality, or any number of things. You're no longer telling the JVM exactly how to do something you are getting closer to telling it what to do and letting it figure out how.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With