A Dance with Java Streams – Part 1

Prologue…

Streams – Yes! I am familiar with this term. I have came across this while working with Java input output library. I remember using InputStream and OutputStream classes. They provide an abstraction over the files and make playing with files real fun.
No idea why did they name new feature of Java 8 “Streams”.
Do they keep any connection with existing input output classes ?

Alright, We will take a look at this…

Beginning…

In SQL world select command means fetching a set of data from a source.
Do they modify the source ? No – They only fetch the data.
Why do we fetch data ? To process it and get the results.
How do we do the processing ? By applying some intermediate operations.
And finally ? We get the desired results.

We fetch some data and do some processing by applying a set of intermediate operations over the data like filtering, mapping etc. and end up with a result set without modifying the actual data present in the table.

Well Done! you got it – I mean you got the basic idea about Streams 🙂

The Java 8 Stream API provides a set of bulk operations to execute over a large data set or simply “Collections”. These data sets acts a source and the computational operations form the pipeline. Once all operations are done we can operate on the processed results.

Remember – We will get the processed results. But the actual data source connected to the stream won’t change after the operations.

Streams are not data structures! We can say a stream is a set of intermediate and terminal operations.

Understanding the Operations…

A stream flow have three different components. A source of data, one or more intermediate operations and a single terminal operation.

As the name says – a terminal operation ends a stream. Once the stream is ended we cannot reuse the stream. But what do intermediate operations do ? – Each of the intermediate operation do some processing on the stream and return another stream in most of the cases for the next intermediate or terminal operation.

Line 1: students.stream().filter().filter().forEach();

Line 1 given above demonstrate a stream flow. It is creating a stream from the students collection and do two filter() operation, and finally a terminal operation forEach(). Let me tell you one important thing. Actual execution of stream operations starts only when the terminal operation is reached. Here the forEach() operation is required to start the execution of preceding two filter() operations.

From the above discussed things, we understood two key points

  • Each of the intermediate operations return a Stream instance in most of the cases for the next intermediate operation.
  • The actual execution won’t start until terminal operation is reached. Or we can say stream intermediate operations are lazy.

What is actual execution ? – Consider the filter operation from the above given line of code. Here the filter operation doesn’t filter anything. But it return a stream which when traversed gives the filtered values. Simply the intermediate operation is a recording about an operation that needs to be performed later when the terminal operation is called. So the actual execution start only when it is triggered using a terminal operation. It is actually a way of minimizing potentially large amount of computation by short circuiting the steps. 🙂

Lambda Expressions…

The line of code given above is just a skeleton which show how we can chain the stream operations. Strictly speaking, the Line 1 given above is incorrect and it is a demo used to show the chaining. Why ? – Because we didn’t specify how the filtering should be done, or how each of the filtered values should be processed. How do we do that? – For this we need to pass a set of operations to the filter() or for forEach() method. We do this by utilizing one of the powerful feature of Java 8 – Lambda Expressions.

Lambda Expressions are introduced with Java 8. It gives the language the power of functional programming. Using Lambda Expressions we can create methods that are not a member of any classes. We can pass these to other methods or executed on demand.

Before we jump into this topic we have to understand the concept of functional interfaces

If an interface specifies only one method without default implementation it is called as functional interface. Beginning with Java 8, we can say methods without default implementation are abstract . And so a functional interface is simply an interface specifying only one abstract method.

See an example of lambda expression and functional interface below.

About the Program…

Here CustomMath interface is a functional interface with a single method called calculate(). This method accepts an integer and return an integer. We will see how the lambda expression comes into picture here.

The new operator -> used here is called the arrow operator or lambda operator. Now consider the first case given in the above code. The left side of the operator is a set of parameters and the right side of the operator is a statement which compute square value. Now take a closer look. The left hand side parameter listing is matching with that of CustomMath calculate() method. Right hand side of the operator is using this variable for the computation and return the square of the integer.

Wait Wait! Where is the return statement here ??? Actually the CASE1 example make use of single line lambda expression and there  we don’t have to specify the return keyword. Post computation it will return the integer. Only thing is the return type should match with the the return type of the CustomMath calculate() method. Otherwise it will lead to compile time error.

But why we used the return keyword in CASE2 ??? The reason is simple. Here we do have more lines of code or simply a code block. So we have to specify the return keyword in the line which is supposed to return the integer. If the method inside functional interface is not returning a type or have void as a return type, then lambda expressions do not or should not return anything.

Hope you got an idea about the streams and lambda expressions basics. We will see how to use lambda expressions and streams together in the next part. Stay tuned. 🙂

Feel free to add your thoughts, comments and suggestions.

Advertisements