Chapter 9 - Java for Beginners Course

Intermediate Operations

The intermediate operations in a stream pipeline are the ones that define how we want to process the data as it arrives.

In this section we’ll cover some of the main intermediate operations that are provided by the Stream interface. We’ll not cover all of the operations defined in the API of the Stream interface but will provide an introduction to the most common ones.

Filter

One of these common operations is to remove elements from a Stream that we’re not interested in. As in our example from the previous section where we only want to keep the even numbers from our original stream:

Example of an Integer Stream of 4 elements with a filter

To achieve this, we can make use of the filter method as follows:

Integer[] array = { 1, 2, 4, 5 };
Stream<Integer> numbers = Stream.of(array) // create the stream
        .filter(n -> n % 2 == 0); // apply a filter

Map

A mapping operation takes the elements of the stream as an input, applies an operation (function) to each element and returns a new stream with the result of the operation.

For example, if we want to change the sign of the numbers in the example above we can apply the following map operation:

Integer[] array = { 1, 2, 4, 5 };
Stream<Integer> numbers = Stream.of(array)
        .map(number -> number * -1);

This would result in the following:

Example of an Integer Stream of 4 elements with a map operation
Even though they have the same name, the map operation is not related to the Map<K,V> data structure that we covered in Chapter 7.

Sorted, Distinct, Skip and Limit

These 4 operations do what you’ll expect and will return a new Stream with the following properties:

Operation Description

sorted()

Returns a Stream with the elements of the original Stream sorted by their natural order. There is another version of this method that accepts a Comparator if you want to sort elements in a different way.

distinct()

Returns a Stream that filters out any repeated elements from the original Stream. The distinct operation relies on the equals method of the objects in the Stream.

skip(n)

Returns a Stream that doesn’t contain the first n elements from the original Stream.

limit(n)

Returns a Stream that contains at most n elements from the original Stream.

Multiple intermediate operations

As mentioned in the previous section, a stream pipeline can define zero of more intermediate operations. To define multiple intermediate operations we can chain method calls as we’ll see below.

Let’s assume we want to take the stream from our first example and 1) apply the change of sign, 2) sort the elements and 3) get the lowest 2 elements:

Integer[] array = { 1, 2, 4, 5 };
Stream<Integer> numbers = Stream.of(array)
        .map(number -> number * -1)
        .sorted()
        .limit(2)

Good practices when working with Streams

As you can see from the examples above we tend to put each operation in a separate line. This is a common practice that is used to help with the readability of code that defines stream pipelines.

A second common practice is to avoid very long bodies inside an operation in a stream pipeline. Instead, you can define a method and use a method reference. This also to help with readability. For example, let’s assume we have the following code:

Integer[] array = { 1, 2, 4, 5 };
Stream<Integer> nums = Stream.of(array)
        .map(n -> {
            if (n % 2 == 1) {
                n = n + 1;
            }
            return n;
        });

The map operation in the middle is changing any odd number (e.g. 1) and adding 1 to them. As such, we could rewrite this as:

public static void main(String args[]) {
    //...
    Stream<Integer> nums = Stream.of(array)
        .map(MyClass::addOneIfOdd);
    //...
}

private static Integer addOneIfOdd(Integer n) {
    if (n % 2 == 1) {
        n = n + 1;
    }
    return n;
}

Important note about Intermediate Operations

As we covered in the previous section, Stream instances are lazy and won’t perform any operations until we provide a terminal operation. For example, consider the following code:

Integer[] array = { 1, 2, 4, 5 };
Stream<Integer> numbers = Stream.of(array)
        .filter(n -> {
            System.out.println("Processing element: " + n);
            return (n % 2 == 0);
        });

A common misconception is that this will print all the elements in the stream. However, with the code as it is, the stream pipeline won’t be executed and no processing will be done.

Once we add a terminal operation, the processing will start and the output will occur as expected. We’ll cover terminal operations in the next section.

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