Java Lambdas and the Stream API

Stream Processing

Your browser needs to be JavaScript capable to view this video

Try reloading this page, or reviewing your browser settings

This segment introduces the concept of a Java Stream and describes how intermediate and terminal operations are used with a Stream.

Keywords

  • Streams
  • Functional Programming
  • Collections
  • Arrays
  • Intermediate Operations
  • Terminal Operations
  • filter
  • map
  • reduce

About this video

Author(s)
Jim McLaughlin
First online
16 November 2019
DOI
https://doi.org/10.1007/978-1-4842-5594-0_3
Online ISBN
978-1-4842-5594-0
Publisher
Apress
Copyright information
© Jim McLaughlin 2019

Video Transcript

In this video, we’ll take a look at Java streams. Streams bringing functional programming to the Java platform and are a powerful way to process data sources like collections or arrays.

So how do streams work? When a stream is created, it’s associated with a data source. Collections and arrays are common data sources for Java Streams. You can think of a stream as a pipeline which our data source objects will be passed through.

Once created, the streams methods provide two types of operations– intermediate operations and terminal operations. Intermediate operations provide ways to transform and filter the objects sent through the pipeline. You can add any number of intermediate operations to your stream processing. Here I show two common operations– map and filter.

Unlike the intermediate operations, there can only be one terminal operation in your stream processing. Most terminal operations return a value, but not all of them do. The forEach terminal operation is one such example, and we’ll take a closer look at forEach in a later video.

Here I show the reduced terminal operation, which is used to reduce the objects being sent through the pipeline down to a single object based on the criteria you can provide. When the stream begins to process the objects from the data source, it passes them off to the operations. The map operation, for instance, is used to transform the object from one type into another type. The filter operation, on the other hand, applies logic to decide if the current object being inspected should continue on in the pipeline or if it should be discarded.

Now, the stream will continue to process the objects from the data source until all the items have been sent through the pipeline. There are some operations which can stop the processing before the items have been sent through the pipeline, and these are called short circuiting operations. We’ll take a look at those a little bit later.

There are three basic steps for using streams in your Java code. First, create the stream associated with your data source. Next, add any intermediate operations you need to transform or filter the data. Finally, add the appropriate terminal operation to your stream processing.

Let’s take a look at the Java doc page for the stream interface. This page gives us some detail on the operations available to us when using streams. As we’ve seen, the main purpose of the stream is to provide computational operations, which are to be performed on the data in the data source. To show these computational operations, I’m going to filter the Java document methods listed to just the instance methods.

These methods will be the list of intermediate and terminal operations you can perform. The stream is responsible for moving the items from the data source through your pipeline of operations. Any operation which returns a stream object is considered an intermediate operation.

This means that we can chain multiple intermediate operations together. These are operations which can sort, filter, or transform the items sent through the pipeline. Instance methods which do not return a stream are considered terminal operations. Some terminal operations return single values while others return multiple values, such as collections or arrays. And then others, like the forEach terminal operation, do not return a value but give you the ability to iterate over all the items passed to it from the pipeline.

Let’s say I have a stream. With intermediate operations, I can sort the items coming through the pipeline and then skip the first three items once the pipeline is sorted. Then I can limit what is passed on to no more than 10 items. Finally, with my terminal operation, I can return the final results as an array.

Let’s take a look at some sample code I’ve put together. Here I have an array list of words. And let’s say I wanted to find the number of distinct words of four more letters. I could always use a for loop to iterate through the list. In that loop, I would have to add the logic to find all the distinct words and also check to see which words are four or more letters.

Now, using streams and lambdas, I don’t have to. Remember the three steps of stream processing. Create a stream, add any intermediate operations, and then add the terminal operation.

Now, in my example, the collection creates the stream for me. As it turns out, the collection interface defines a simple method for creating a stream. Once I have the stream, I can perform the distinct intermediate operation which removes duplicates. This allows only unique values to be passed on in the pipeline.

Next, I can filter the items remaining so that only words of four more letters will be passed on. This is done with the filter operation. The powerful thing about the filter operation is that it allows me to define the filter criteria. It does this by accepting a functional interface called a predicate.

As you can see from the Java doc, the predicate in the filter operation is applied to each element in the stream to determine if it should be passed on. That means my lambda, which implements the predicate functional interface, will return true for items I want to keep in the pipeline and returns false for the ones I wish to discard. Of course, I can always simplify the lambda because the predicate functional interface only takes one parameter and the lambda has only one statement. So let’s simplify the lambda by removing the parentheses, the curly brackets, the terminating semicolon, and finally the return keyword.

OK, and last but not least, we have the terminal operation count. And it does nothing but return the count of all the items remaining in the pipeline. OK, so let’s run it and see the output. There we go. The collection has four distinct words of four or more characters.

Let’s review. Strings provide a way to pipeline objects through computational operations. Intermediate operations can be chained together because their return type is of type stream. And finally, terminal operations are used to terminate the pipeline and may or may not return a value.