Java 8 Streams API – Stream Collectors

This tutorial covers examples of Java 8 Stream Collectors, their type, and usage. The Stream Collectors are used as part of the Terminal operations and appear in the last stage of Streams pipeline.

Java 8 Streams Collectors

In this tutorial we will discuss various Collectors available in Java Streams API. Previously, we had seen an Overview of Java 8 Streams, Intermediate Operations of Java 8 Streams, and Terminal Operations of Java 8 Streams.

The collectors sit in the last of a stream pipeline and help to accumulate the elements of stream is the form of data structures, this is nothing but the final output of the stream pipeline. The class Collectors is an utility class which has many factory methods to create various predefined collectors implementations.

The Collectors helps to reduce, summarise, group, and partition the elements in a stream. Although, there are many useful predefined collectors available, we can also write our own Stream Custom collectors.

Overview of Stream.collect() Method

The collect() method is a Terminal Method on the streams pipeline. Which means, the method appear at the end of the pipeline. It performs mutable reduction operations on the elements of the stream to transform them into desired data structures.

Typically, elements from a collection are streamed at the beginning of a Stream pipe line. Next, the elements in the stream are operated, transformed, filtered, or rearranged using various intermediate operations. Finally using the terminal method of collect() the elements in the stream can be transformed back into desired collection. This transformation strategy is defined by the Collector, which is passed as a parameter to the collect() method.

Java 8 Streams API provides many predefined implementations of Collector interface via its implementation class of Collectors. We will see some of the collectors methods in the coming section.

Collect Stream elements as List

Simplest way to collect Stream elements into a List is to used toList() method.

List<Integer> inputList = List.of(1, 2, 3, 4, 5 ); List<Integer> outputList = inputList .stream() .collect(Collectors.toList());
Code language: Java (java)

This collector by default accumulate elements in a new ArrayList. In order to use other implementations of List, we can use toCollection() method instead. For example, the in the next snippet, the elements are collected in a LinkedList.

List<Integer> linkedList = inputList .stream() .collect(Collectors.toCollection(LinkedList::new));
Code language: Java (java)

Collect Stream elements in UnmodifiableList()

In order to collect stream elements into an Unmodifiable List, we can make use of toUnmodifiableList() method.

List<Integer> unmodifiableList = inputList .stream() .collect(Collectors.toUnmodifiableList());
Code language: Java (java)

Collect Stream elements as Set

In order to collect the stream elements into a Set, we can use toSet() collector.

Set<Integer> outputSet = inputList .stream() .collect(Collectors.toSet());
Code language: Java (java)

The toSet() collector by default collects the elements into a new HashSet instance.
However, we can use toCollection() method with desired constructor supplier. For example, in the next snippet, we are collecting the elements in a TreeSet.

Set<Integer> treeSet = inputList .stream() .collect(Collectors.toCollection(TreeSet::new));
Code language: Java (java)

Collect Stream elements in UnmodifiableSet()

Similarly, we can collect the stream elements as part of an Unmodifiable Set using toUnmodifiableSet() method.

Set<Integer> unmodifiableSet = inputList .stream() .collect(Collectors.toUnmodifiableSet());
Code language: Java (java)

Collect Stream elements as Map

Using the toMap() collectors, we can collect stream elements into a Map. However, in order to create the map entries we need to provide key and value pairs. This is done by passing key function and value functions to the collector method.

List<Student> students = List.of( new Student(1231L, "Strong", "Belwas"), new Student(42324L, "Barristan", "Selmy"), new Student(15242L, "Arthur", "Dayne") ); Map<Long, String> idStudentNameMap = students .stream() .collect(Collectors.toMap(Student::getId, Student::getFirstName));
Code language: Java (java)

In the toMap() method we are passing two arguments, which are references to the Id and First Name getter methods. Now, when we print the accumulated map we get all the key value pairs.

{42324=Barristan, 15242=Arthur, 1231=Strong}

Collect Stream elements in UnmodifiableMap()

To create an Unmodifiable Map from the stream elements, we can use toUnmodifiableMap() method.

Map<Long, String> unmodifiableMap = students .stream() .collect(Collectors.toUnmodifiableMap(Student::getId, Student::getFirstName));
Code language: Java (java)

To lean more about converting a List to Map using Streams please visit Examples of Converting List to Map using Streams.

Join String elements from a Stream

Using the collectors implementation of joining(), we can concatenate String elements together.

The next example shows a Stream of Student, where the joining() method is used to concatenate the last names of all the students separated by comma. It also passes a pair of prefix and suffix.

String concatenatedLastNames = students .stream() .map(Student::getLastName) .collect(Collectors.joining(",", "[", "]"));
Code language: Java (java)

Printing the output String we get the desired result

[Belwas,Selmy,Dayne]

Stream Collectors for numbers

We will go through some of the ready to use collectors, specifically used on the numerical data in Stream.

Summarise Numerical Data in Stream

The Collectors provide useful methods which can be used on a Stream to generate useful metrics like sum, average, min and max using the numerical data (Integer, Long, or Double) within the stream elements.

List<Student> students = List.of( new Student(1231L, "Strong", "Belwas", 50), new Student(42324L, "Barristan", "Selmy", 34), new Student(15242L, "Arthur", "Dayne", 40) ); IntSummaryStatistics intSummaryStatistics = students .stream() .collect(Collectors.summarizingInt(Student::getAge)); System.out.println(intSummaryStatistics.getMax()); System.out.println(intSummaryStatistics.getMin()); System.out.println(intSummaryStatistics.getCount()); System.out.println(intSummaryStatistics.getAverage());
Code language: Java (java)

In this example, we are iterating through the stream of Student and generating statistical data based on the integer field of age using summarizingInt() method. The result of the statistical analysis is collected in a wrapper called as IntSummaryStatistics, from which we can get various stats like min, max, count and average to get the next result

 50
 34
 3
 41.333333333333336

Similarly, we can also generate statistical data for Double, and Long numbers to get the statistics in the form of DoubleSummaryStatistics and LongSummaryStatistics respectively.

The summarizing collector is useful when, we want to pull various statistics about the numerical data. However, if we are interested only the specific metrics then there are collectors, which returns individual metrics. Next, we will see such collectors.

Count the elements in Stream

The counting() method on the Collectors can be used to simply count the total number of elements in the stream.

Long count = students .stream() .collect(Collectors.counting());
Code language: Java (java)

Sum of Numbers in Stream

Collectors provide a way to sum up all the numeric data within a Stream. The methods summingInt(), summingDouble(), and summingLong() can be used to get sum of Integer, Double, and Long numbers respectively.

int sum = students .stream() .collect(Collectors.summingInt(Student::getAge));
Code language: Java (java)

Average of Numbers in Stream

We can find average of the numberical data from the stream using averagingInt(), averagingDouble(), and averagingLong() for integer, double, and long numbers respectively.

double average = students .stream() .collect(Collectors.averagingInt(Student::getAge));
Code language: Java (java)

Find min and max elements in Stream

Using the collector functions of minBy() and maxBy() and passing a comparator by can find the min and max element out of the stream.

Optional<Student> student = students .stream() .collect(Collectors .minBy(Comparator.comparingInt(Student::getAge)));
Code language: Java (java)

In this example, we are using minBy() collector on the Student stream and passing it a comparator comparing based on the age. The we get is the youngest student.

Similarly we can find the oldest student by using maxBy().

Group elements from Stream

The collector groupingBy() is used to group elements from the Stream. The first argument to this method is a classifier based on which the elements will be grouped. Moreover, we can pass another collector to specify how the grouped elements should be combined.

Map<String, List<Student>> nameToStudentMap = students .stream() .collect(Collectors.groupingBy(Student::getFirstName, Collectors.toList()));
Code language: Java (java)

In this example, we are grouping all the students with same first name. Students with the same name will be grouped together in a List. As an output we get a Map containing first name as a key, and list of grouped students as its value.

Partition elements in Stream

The partitionBy() collectors is used to make two groups from the elements of a Stream. We can pass a predicate to the method based on which the method will create two collections, containing the elements that match the given predicate and the elements that don’t. The output will be a Map where key is the boolean and values will be the partitioned collections.

Map<Boolean, List<Student>> partitionedStudents = students .stream() .collect(Collectors.partitioningBy(s-> s.getFirstName().toLowerCase().startsWith("a")));
Code language: Java (java)

In this example, the predicate finds the students, who’s first name starts with ‘a’. In other words, we are partitioning the students stream based on whether their first name starts with ‘a’ or not.

Summary

In this tutorial we have covered Java 8 Stream Collectors. The collectors are used along with terminal operation of collect() and used for collecting elements from a Stream.

To learn more about Java 8 Streams please visit Introduction to Java 8 Streams API.