Java 8 Streams
A stream is an infinite sequence of consumable elements (i.e a data structure) for the consumption of an operation or iteration. Any Collection<T> can be exposed as a stream. It looks complex, but once you get it, it is very simple. The operations you perform on a stream can either be
1. Intermediate operations like map, filter, sorted, limit, skip, concat, substream, distinct, peek, etc producing another java.util.stream.Stream<T> or a
2. Terminal operations like forEach, reduce, collect, sum, max, count, matchAny, findFirst, findAny, etc producing an object that is not a stream.
Basically, you are building a pipeline as in Unix. In Unix we “pipe” operations, and in Java 8, we stream them.
The stream( ) is a default method added to the Collection<T>interface in Java 8. The stream( ) returns a java.util.starem.Stream<T> interface with multiple abstract methods like filter, map, sorted, collect, etc. TheDelegatingStream<T> is the implementing class.
Intermediate operations are lazy operations, which will be executed only after a terminal operation was executed. So when you call .filter(i -> i % 3 == 0) the lambda body isn’t being executed at the moment. It will only be executed after a terminal operation was called (collect, in the example shown below). This is essential to understand from the viewpoint of adding break points in your IDE for debugging purpose.
Go through these examples to get a good handle on the stream concepts.
11 numbers 1 to 10 and an extra 6 are a) filtered first for multiples of 3 b) filtered for values less than 7 c) remove duplicates by adding to a Set<T> d) print the result.
i -> i % 3 == 0 is a lambda expression used as a predicate to filter only multiples of 3. So,
Q. what is this “lambda expression”?
A. In OOP or imperative programming, x = x+ 5 makes sense, but in mathematics or functional programming, you can’t say x = x + 5 because if x were to be 2, you can’t say that 2 = 2 + 5. In functional programming you need to say f(x) -> x + 5.
A. In OOP or imperative programming, x = x+ 5 makes sense, but in mathematics or functional programming, you can’t say x = x + 5 because if x were to be 2, you can’t say that 2 = 2 + 5. In functional programming you need to say f(x) -> x + 5.
Java 8 Stream
Example 1:
In the above example, filter and peek are intermediate operations that return a “Stream<T>” object. The “peek” is used for debugging. The “collect(…)” is a terminal operation that returns a “Collection<T>” object, which extends “Iterable<T>”interface which has the “forEach(…)” method. Don’t confuse this with the “forEach()” method in the “java.util.stream.Stream<T>”.
Output:
Example 2:
Same as above, let’s introduce another terminal operation sum().
In the above example, filter, peek, and mapToInt are intermediate operations that return a “Stream” object. “sum” is terminal operation that returns a result.
Output:
Example 3:
Let’s mix “intermediate” and “terminal” operations up.
In the above example, filter(..), peek(..), stream(..), and mapToInt(..) are intermediate operations that return a “Stream<T>” object. “collect(…)” and “sum()” are terminal operations. Since, “collect” returns a “Collection<’;T>” terminal object after removing the duplicate value of 6 with the help of toSet(), we need to call the stream() again to get the “Stream<T>” object back. Finally, “sum()” is a terminal operation.
Output:
So, if still having trouble grasping this, have a look at the Java 8 API docos for Interfaces Stream<T>, Iterable<T> and Interface Collection<E>. Pay attention to default methods and return objects.
So, now with a little bit of help from the Java 8 API docs, you can perform different combination of operations on a collection of data. You can also debug by placing break points in your IDE like eclipse by keeping in mind thatintermediate ops are lazily evaluated after a terminal operation. The peek() intermediate operation is very handy for debugging as well.