Stream API and Parallelism

The Stream API is a new feature in Java 8 that provides advanced capabilities to manipulate large collections of data. It is one of the most significant new features in Java 8 because of its ability to support sophisticated operations and execute parallel tasks. By using the Stream API you can specify SQL query-like operations to search, filter, or map data without the need to write lengthy code. The Java 8 Stream API is a part of the Collections framework and is used together with lambda expressions.

What is a Java 8 stream ?

A stream is a sequence of objects that processes data obtained from a collection, such as an array, a list, or a set. Streams do not store data because the purpose of a stream is only to move data and perform operations on it. Therefore, whenever an operation is performed on a stream, the elements in the stream are not modified. The results of the operation are simply moved to a new stream. In general, a stream obtains data from a source, processes the data through a pipeline of operations, and produces results at the end using a terminal operation.

Note: The Stream API in the Collections framework is different from the I/O streams in Java.

The Stream API is available as part of the java.util.stream package through the BaseStream and Stream interfaces.

Types of Stream Operations

There are two types of stream operations. They are:

Intermediate operations:

The operations on a stream that result in another stream are called intermediate operations. A stream can have several intermediate operations in a pipeline for performing a sequence of tasks.

Intermediate operations implement lazy behaviour. Though an intermediate operation creates a new stream, it begins to execute only when a terminal operation is executed on the new stream. Examples of intermediate operations include filtering and sorting operations.

Terminal operations:

The operations on a stream that produces a result are called terminal operations. A terminal operation is a final operation in a pipeline that consumes a stream to produce a result. For example, finding the maximum value in a stream is a terminal operation.

Note: The stream used by a terminal operation is fully consumed and cannot be reused by another operation.

Common Stream Operations

The following are some common operations on streams:

Filter operation:

Filters the items in a stream based on specific criteria using the filter() method.

Map operation:

Maps the items in one stream to another stream using the map() method.

Collect operation:

Collects elements from a stream using the collect() method and stores them to a collection.

Min, Max, and Count operations:

Use the min() and max()methods to find the minimum and maximum values in a stream respectively. The count() method is used to find the number of elements in a stream.

Reduce operation:

Applies specific criteria to obtain a single value from a stream using the reduce() method.

Stream Example

The following program is a simple example that performs several operations on a list of integers using streams.

StreamOperations.java
import java.util.ArrayList;
import java.util.Optional;
import java.util.stream.Stream;

public class StreamOperations {

public static void main(String[] args) {

ArrayList<Integer> list1 = new ArrayList<>();
list1.add(17);
list1.add(48);
list1.add(20);
list1.add(14);
list1.add(55);
list1.add(25);
System.out.println("Original list: " + list1);

Stream<Integer> strm = list1.stream();

Optional<Integer> minv = strm.min(Integer::compare);
if (minv.isPresent()) {
System.out.println("Minimum value: " + minv.get());
}

Stream<Integer> sortedstrm = list1.stream().sorted();
System.out.print("Sorted stream: ");
sortedstrm.forEach((n) -> System.out.print(n + " "));
System.out.println();

Stream<Integer> oddv
= list1.stream().sorted().filter((n) -> (n % 2) == 1);
System.out.print("Odd values: ");
oddv.forEach((n) -> System.out.print(n + " "));
System.out.println();
}
}

Output:

Original list: [17, 48, 20, 14, 55, 25]
Minimum value: 14
Sorted stream: 14 17 20 25 48 55
Odd values: 17 25 55

In this program, strm is a stream that is obtained from the array list list1 using the stream() method. By using the min() method, the minimum value in this stream is identified and then displayed. In the next step, the sorted()method is used to sort the elements in the array list and obtain the results in the sortedstream stream.

Finally, the program uses a pipeline of operations to identify the odd values in the array list. The filter() method is used to filter the sorted stream to identify odd numbers and return the results into the oddv stream. Note that, throughout the program, lambda expressions are used along with the operations to perform the required functions.

The output of this program is given below:

Original list: [17, 48, 20, 14, 55, 25]

Minimum value: 14

Sorted stream: 14 17 20 25 48 55

Odd values: 17 25 55

In this program, strm is a stream that is obtained from the array list list1 using the stream() method. By using the min() method, the minimum value in this stream is identified and then displayed. In the next step, the sorted()method is used to sort the elements in the array list and obtain the results in the sortedstream stream.

Finally, the program uses a pipeline of operations to identify the odd values in the array list. The filter() method is used to filter the sorted stream to identify odd numbers and return the results into the oddv stream. Note that, throughout the program, lambda expressions are used along with the operations to perform the required functions.

Parallel Streams

The Stream API enables you to create parallel streams that can take advantage of multi-core architectures and enhance the performance of Java code. In a parallel stream, the operations are executed in parallel. There are two ways to create a parallel stream. They are:

  • Using the parallelStream() method on a collection

  • Using the parallel() method on a stream

Example

Optional<Integer> calcProd = list1.parallelStream().reduce((a,b) -> a*b));

In this code, a parallel stream is obtained from list1 and the reduction operation is performed on it.

Parallel streams must be used only with stateless, non-interfering, and associative operations. A stateless operation is an operation in which the state of one element does not affect another element. A non-interfering operation is an operation in which data source is not affected. An associative operation is an operation in which the result is not affected by the order of operands.

Happy Learning 🙂