Chapter 9 - Java for Beginners Course

Terminal Operations

The terminal operation in a stream pipeline defines how we want to produce/generate a result, or how do we want to define a final process for each element in our resulting Stream.

In this section we’ll cover some of the most common types of terminal operations provided by Java.

Accumulating Collections

An usual desired result is to retrieve the elements from a Stream<T> and put them in a List<T>, Set<T>, or Map<K,V> either for further processing or to return them to a calling method.

To achieve this, we can use the collect terminal operation. This method allows us to provide a Collector that we can use to accumulate the elements of the stream.

Java provides several implementations out of the box in the Collectors utility class, like Collectors.toList() that will place all resulting elements from the Stream<T> into a List<T>.

Let’s look at some code using the same example we introduced before:

Example of an Integer Stream of 4 elements with a filter and a terminal operation
In the example code run the JavaBasicIntegerStreamApp
Integer[] array = { 1, 2, 4, 5 };
List<Integer> result = Stream.of(array)
        .filter(n -> n % 2 == 0)
        .collect(Collectors.toList());

for (int number : result) {
    System.out.println(number);
}

Output:

2
4

In this particular example, as we’re using the Collectors.toList() collector, our result is expected to be a List<Integer>.

In a similar way you can use Collectors.toSet() to get the result as a Set<T> data structure. There are more predefined Collectors offered that will be covered in a separate course.

Reduce

A reduction is the process of combining/processing all the elements to generate an accumulated result. To continue with our example Integer stream, a reduction operation would be to return the sum of all the elements, or to find the maximum, or the standard deviation, among many others.

The Stream<T> interface defines a set of reduce methods for this purpose. For example, to perform a sum reduction we could do the following:

In the example code run the JavaBasicStreamReduceApp
Integer[] array = { 1, 2, 3 };
Integer sum = Stream.of(array)
        .reduce(0, (num1, num2) -> num1 + num2);

System.out.println("The sum is: " + sum);

Output:

The sum is: 6

The reduce method behind the scenes will invoke the operation (in our case a sum num1 + num2) for each element in the stream and accumulate the result. The next table summarizes the internal steps for this example:

Step Current Result Next Element Operation New Result

1

0

1

0 + 1

1

2

1

2

1 + 2

3

3

3

3

3 + 3

6

The Current Result starts with a value of 0 as that’s the initial value/identity we’re passing to the reduce method as the first parameter.

Special cases of reductions

The Stream API comes with a set of predefined reductions that cover some common cases. These special cases include:

Operation Description

count()

Returns the number of elements in the stream.

max(…​) and min(…​)

These methods return the maximum/minimum element from a stream respectively. Both methods receive a Comparator as their input that is used to decide if an element is greater than or less than another element.

If you take a look at the primitive streams, like IntStream, they also define additional cases of reduction out of the box:

Operation Description

count()

Returns the number of elements in the stream.

max() and min()

These methods return the maximum/minimum element from a stream respectively.

sum()

Returns the sum of the elements in the stream.

average()

Returns the average of the elements in the stream.

For example, we could do the sum example above using the special reduction case:

In the example code run the JavaBasicIntStreamSumApp
int[] array = { 1, 2, 3 };
Integer sum = IntStream.of(array).sum();
System.out.println("The sum is: " + sum);

Output:

The sum is: 6

Execute an Action per element

In some scenarios we might not want to collect/reduce our elements, but instead we want to execute a specific action for each resulting element in our stream pipeline.

To do this, we make use of the forEach method in the Stream API. The forEach method receives as a parameter the action we want to execute per element.

For example, lets assume we want to print out all the elements in our Integer stream after multiplying each element by 2:

In the example code run the JavaBasicIntegerStreamTimesTwoApp
Integer[] array = { 1, 2, 4, 5 };
Stream.of(array)
        .map(n -> n * 2)
        .forEach(n -> System.out.println(n));

Output:

2
4
8
10

More terminal operations

There are more terminal operations than the ones we cover in this section. We’ll cover more intermediate and terminal operations in the remainder of this course and in separate courses.