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.
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:
- Source
- Intermediate operations
- 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.
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:
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.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.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.sorted()
: This operation sorts the elements of a stream based on a given comparator. It creates a new stream that contains the sorted elements.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.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.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:
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.count()
: This operation returns the number of elements in a stream as a long value.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.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.min()
: This operation returns the minimum element of a stream based on the elements' natural order or a given comparator.max()
: This operation returns the maximum element of a stream based on the elements' natural order or a given comparator.anyMatch()
: This operation returns true if any stream element matches a given predicate.allMatch()
: This operation returns true if all stream elements match a given predicate.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.
Gopi Gorantala Newsletter
Join the newsletter to receive the latest updates in your inbox.