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.

Code in GitHub

Get the code for this tutorial using the links below.

Project Repo
Download code for this step
Main class for this step
Dependencies

This is a list of recommended tutorials or courses that might be useful before starting this one.

Contents
Welcome to the Course!
Course Introduction
Chapter 1 - Building Blocks
Quick introduction to Java Variables Classes And Objects Class Example - Defining a class Object Examples - Creating instances Java Application Example - Running our first app Accessing class members - The dot operator Packages - Organizing the code
Chapter 2 - Primitives and Operators
Primitives Arithmetic Operators Assignment Operator Unary Operators Equality and Relational Operators Conditional Operators
Chapter 3 - Statements and Control Flow
Expressions Statements If-Then Statement If-Then-Else Statement More If Statements Switch Statement While and Do-While Statements For Statement Branching Statements Exception Handling
Chapter 4 - Code Example
Example Project - A Simple Vending Machine Adding money Delivering Items Giving Change
Chapter 5 - Classes and Interfaces
Introduction Access Level Modifiers Class Declaration - Class, Methods and Fields Class Declaration - Constructors Inheritance Basics Inheritance - Constructors Inheritance - Methods and Fields Polymorphism Abstract Classes and Methods Interfaces Static Class Members Class Composition Final Classes and Class Members Generic Classes
Chapter 6 - Base Object Behaviors
Introduction Type Comparison Type Casting Object Equality - The Contract Object Equality - Common Pitfalls Object String Representation Garbage Collection Object Comparison Primitive Wrappers and Autoboxing
Chapter 7 - Data Structures
Introduction Arrays - Declaration and Creation Arrays - Basic Operations Core Collection Interfaces List and ArrayList - Basic Operations ArrayList Internals Introduction to Hash Tables Map and HashMap - Basic Operations Set and HashSet - Basic Operations
Chapter 8 - Anonymous classes and lambdas
Introduction Filtering a List Anonymous Classes Lambdas Built-in Functional Interfaces
Chapter 9 - Streams
Introduction Creating Streams Intermediate Operations Terminal Operations