Java 8 Streams API – Stream Collectors

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

Java 8 Streams Collectors

This tutorial 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 accumulate the Stream’s elements in the form of data structures; this is nothing but the final output of the stream pipeline. The class Collectors is a utility class that has many factory methods to create various predefined collector implementations.

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

Overview of Stream.collect() Method

The collect() method is a Terminal Method on the stream pipeline. This means the method appears 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 pipeline. Next, the elements in the Stream are operated, transformed, filtered, or rearranged using various intermediate operations. Finally, we can use the collect(collector) method, to collect the Steram elements into a Java object.

The Collectors class of the Java Streams API provides several collector abstractions that we will see in the following sections.

Collecting Stream elements as a Java List

The simplest way to collect Java Stream elements in a List is to use the terminal method toList().

Example of collecting Java Stream elements in a List.

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5 );
List<Integer> list = stream.toList();Code language: Java (java)

The toList() method creates and returns a new ArrayList containing all the elements from the Stream. However, if we want to collect the Stream elements in a different implementation of List or into an existing List we can provide it using the toCollection() method of the Collectors.

Example of collecting Java Stream elements in a LinkedList.

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

Collect Stream elements in UnmodifiableList()

To collect stream elements into an Unmodifiable List, we can use the toUnmodifiableList() method.

Example of collecting Java Stream elements in an immutable List.

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

Collecting Stream elements as a Java Set

Let’s use the toSet() method of the Collectors to collect all the Stream elements as a HashSet.

Example of collecting Java Stream elements as a HashSet.

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

The collector creates and returns a new HashSet instance containing all the elements of the Stream.

To collect the Stream elements into an existing Set or to collect the Stream in a different implementation of Set, we can use the toCollection() method.

Example of collecting Java Stream elements as a TreeSet.

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

Collect Stream elements as UnmodifiableSet()

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

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

Collect Stream elements as Map

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

Example of collecting Java Stream elements as a Map.

Stream<Student> stream = Stream.of(
  new Student(1231L, "Strong", "Belwas"),
  new Student(42324L, "Barristan", "Selmy"),
  new Student(15242L, "Arthur", "Dayne")
);
        
Map<Long, String> map = stream.collect(Collectors
    .toMap(Student::getId, Student::getFirstName));

System.out.println(map);
//prints:
//{42324=Barristan, 15242=Arthur, 1231=Strong}Code language: Java (java)

Collect Stream elements in UnmodifiableMap()

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

Example of collecting Java Stream elements as an immutable Map.

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

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

Join Elements from a Stream

Using the joining() method of the Collectors, we can concatenate elements of Java Streams.

Example of joining String elements from a Java Stream with a prefix and suffix.

String concatenatedLastNames = studentStream
    .map(Student::getLastName)
    .collect(Collectors.joining(",", "[", "]"));

System.out.println(concatenatedLastNames);
//prints:
//[Belwas,Selmy,Dayne]Code language: Java (java)

Stream Collectors for numbers

Let’s quickly see some predefined Collectors that work with a Stream of numbers.

Summary Statistics From numerical Java Streams

The Collectors class provides a powerful abstraction that generates many useful stats from the numbers within a Java Stream. The methods summarizingInt(), summarizingDouble(), and summarizingLong(), respectively analyze given numeric fields and returns the summary statistics as part of IntSummaryStatistics, DoubleSummaryStatistics, or LongSummaryStatistics instances respectively.

Example generating IntSummaryStatistics using Java Stream’s Summary Statistics Collector.

Stream<Student> students = Stream.of(
    new Student(1231L, "Strong", "Belwas", 50),
    new Student(42324L, "Barristan", "Selmy", 34),
    new Student(15242L, "Arthur", "Dayne", 40)
);

IntSummaryStatistics intSummaryStatistics = 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());

//print:
//50
//34
//3
//41.333333333333336Code language: Java (java)

Counting the elements in a Java Stream

The counting() method on the Collectors class counts and returns the total number of elements in the Stream.

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

Sum of Numbers in a Java Stream

To get a sum of the Integer, Double, or Long numbers from a Java Stream we can use the summingInt(), summingDouble(), or summingLong() methods of the Collectors class, respectively.

Example of summing all the Integer numbers in a Java Stream.

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

Average of Numbers in a Java Stream

Similarly, the averagingInt(), averagingDouble(), and averagingLong() methods of the Collectors class counts and return the average of the numeric data of the respective types.

Example of calculating the average of all Integers in a Java Stream.

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

Find min and max elements in Stream

We can use the minBy() and maxBy() collectors of the Collectors class. We can find the minimum and maximum values of the Stream, respectively.

Example of finding the youngest student from a Stream.

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

Example of finding the oldest student from a Stream.

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

Grouping the Java Stream Elements

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. We can pass another collector to specify how the grouped elements should be combined.

Example of grouping the Stream elements.

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

Partitioning the Java Stream Elements

The partitioningBy() method of the Collectors class takes a Predicate. It returns two Lists of the Stream elements, one that contains the elements matching the Predicate and the other containing elements that don’t.

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

Summary

This was an in-depth guide to Java Streams Collectors. We understood that Java Stream collectors could help us collect Java Stream elements or data derived from the elements into different Java objects. We had a detailed overview of built-in abstractions provided by the Java Streams Collectors class.

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