Skip to content

How Does Streams API Work?

In this lesson, you will learn everything you need to understand what are streams. and how they actually process data.

Gopi Gorantala
Gopi Gorantala
4 min read

Table of Contents

Introduction

Java Streams API provides a functional programming approach for processing collections of objects. It works by allowing you to define a pipeline of operations that will be executed on the collection in a lazy and efficient manner.

Before you learn how streams work. You need to understand the flow of operations it has in various stages in the stream pipeline.

Stream pipeline

Java Streams API creates a pipeline of operations that can be performed on a sequence of elements.

The pipeline comprises a source, zero or more intermediate operations, and a terminal operation.

Here is each part of the pipeline:

  1. Source
  2. Intermediate operations
  3. Terminal operations

It's important to note that intermediate operations are lazy, meaning they do not perform any processing until a terminal operation is called. This allows the pipeline to be optimized and potentially executed in parallel.

Streams also support parallel processing, meaning the pipeline can be divided into smaller sub-pipelines and executed in parallel on multiple cores. You call the parallel() method on the stream to enable parallel processing.

Overall, the Streams API provides a powerful and efficient way to process collections of data in Java, allowing developers to write concise and expressive code that can be easily optimized for performance.

Stream pipeline flow

Following is a simple sketch that explains the stream pipeline flow in detail.

Streams API - High-level design
Streams API - High-level design

Source

The source of a stream can be a collection, an array, an I/O channel, or any other data source. To create a stream, you typically call the stream() or parallelStream() method on a collection or array.

For example, to create a stream from a List, you can use the stream() method.

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class StreamDemo {
  public static void main(String[] args) {
    List<String> fruits = Arrays.asList("apple", "banana", "orange");
    Stream<String> fruitsStream = fruits.stream();
    fruitsStream.forEach(
        fruitName -> {
          System.out.println(fruitName);
        });
  }
}

This creates a stream of String objects from the List object fruits.

The above snippet prints the following result on the console:

apple
banana
orange

Similarly, you can create a stream from an array using the Arrays.stream() method:

import java.util.Arrays;
import java.util.stream.IntStream;

public class ArrayStream {
  public static void main(String[] args) {
    int[] myArray = {1, 2, 3};
    IntStream myStream = Arrays.stream(myArray);
    myStream.forEach(System.out::println);
  }
}

This creates an IntStream object from the integer array myArray.

Once you have a stream source, you can apply intermediate and terminal operations on the stream to process the data. Intermediate operations include filter(), map(), flatMap(), distinct(), sorted(), limit(), and skip(). Terminal operations include forEach(), collect(), and reduce().

Intermediate operations

Intermediate operations in the Java Streams API transform and filter the elements of a stream to produce a new stream. They are called intermediate operations because they do not produce a final result but instead create a new stream that can be further processed or used as a source for a terminal operation.

Here are some examples of intermediate operations in the Streams API:

  1. filter(): This operation filters the elements of a stream based on a given predicate. It creates a new stream that contains only the elements that satisfy the predicate.
  2. map(): This operation transforms the elements of a stream by applying a given function to each element. It creates a new stream that contains the transformed elements.
  3. flatMap(): This operation is similar to map() but can transform each stream element into zero or more elements of a new stream. It creates a new stream that contains all of the transformed elements.
  4. sorted(): This operation sorts the elements of a stream based on a given comparator. It creates a new stream that contains the sorted elements.
  5. distinct(): This operation removes duplicates from a stream based on the elements' natural order or a given comparator. It creates a new stream that contains only the distinct elements.
  6. limit(): This operation limits the size of a stream to a given number of elements. It creates a new stream that contains only the first n elements of the original stream.
  7. skip(): This operation skips the first n elements of a stream and creates a new stream that contains the remaining elements.

Intermediate operations can be combined and chained together to create a pipeline of operations that transform and filter the elements of a stream. The resulting stream can be further processed or used as a source for a terminal operation.

Terminal operations

Terminal operations in the Java Streams API perform a final action on the elements of a stream and produce a result. They are called terminal operations because they are the last operation in the stream pipeline and trigger the processing of the elements.

Here are some examples of terminal operations in the Streams API:

  1. forEach(): This operation applies a given action to each element of a stream. It does not produce a result but can perform a side effect for each element.
  2. count(): This operation returns the number of elements in a stream as a long value.
  3. reduce(): This operation combines the elements of a stream using a given binary operator and returns the result. It can perform calculations like sum, product, or maximum value.
  4. collect(): This operation collects the elements of a stream into a collection or other data structure. It can be used to create lists, sets, maps, or arrays from the elements of a stream.
  5. min(): This operation returns the minimum element of a stream based on the elements' natural order or a given comparator.
  6. max(): This operation returns the maximum element of a stream based on the elements' natural order or a given comparator.
  7. anyMatch(): This operation returns true if any stream element matches a given predicate.
  8. allMatch(): This operation returns true if all stream elements match a given predicate.
  9. noneMatch(): This operation returns true if no stream element matches a given predicate.

Terminal operations are the final step in a stream pipeline, and they trigger the processing of the elements. Once a terminal operation is called, the stream cannot be used again. Terminal operations can be used to perform calculations, create new data structures, or perform side effects on the elements of a stream.

Java Streams APIJava

Gopi Gorantala Twitter

Gopi is an engineering leader with 12+ of experience in full-stack development—a specialist in Java technology stack. He worked for multiple startups, the European govt, and FAANG in India and Europe.

Comments


Related Posts

Members Public

Differences Between JDK, JRE, and JVM?

Short answer JDK, JRE, and JVM are essential components of the Java platform, each serving a distinct purpose. Here are the key differences between them: 1. JDK (Java Development Kit): The JDK is used by developers to write, compile, and debug Java code. 2. JRE (Java Runtime Environment): End-users use

Members Public

Difference Between String and char[] in Java

Short answer Strings String is an object with many helpful methods. String class in Java's standard library is designed to handle text as a sequence of characters. A string of characters (string object) is non-modifiable or immutable in Java. Once you've created it, you cannot modify

Members Public

What is an Object class in Java?

Short answer Object class is the super class of every class you can create. In Java, a class extends another class using the keyword extends. If you don't have any other class to extend, that's fine. The compiler will make your class extend the Object class.