Java Stream Collectors

Introduction

In Java, streams provide a powerful and concise way to process collections of data. Streams allow us to perform operations on data in a declarative manner, resulting in more readable and maintainable code.

One of the key features of streams is the ability to collect the processed data into a collection or a single value. Java provides the Collectors class, which offers a set of predefined collectors that can be used to accumulate elements into various data structures. In this article, we will explore the Collectors class and see how it can be used to collect data in different ways.

Basic Collectors

The Collectors class provides several basic collectors that can be used to perform common collection operations. Let's start by looking at some of the commonly used collectors:

toList()

The toList() collector collects the stream elements into a List. It can be used as follows:

List<String> names = students.stream()
                            .map(Student::getName)
                            .collect(Collectors.toList());

This code snippet collects the names of students into a list.

toSet()

Similarly, the toSet() collector collects the stream elements into a Set:

Set<String> uniqueNames = students.stream()
                                 .map(Student::getName)
                                 .collect(Collectors.toSet());

This code snippet collects the unique names of students into a set.

toMap()

The toMap() collector collects the stream elements into a Map. It takes two functions: one for extracting the keys and another for extracting the values. Here's an example:

Map<Integer, String> idToNameMap = students.stream()
                                           .collect(Collectors.toMap(Student::getId, Student::getName));

This code snippet collects the student IDs as keys and student names as values into a map.

joining()

The joining() collector concatenates the stream elements into a single String. We can specify an optional delimiter to be used between the elements. Here's an example:

String names = students.stream()
                       .map(Student::getName)
                       .collect(Collectors.joining(", "));

This code snippet collects the names of students into a comma-separated string.

Aggregation and Reduction

Apart from basic collectors, the Collectors class provides collectors for performing aggregation and reduction operations on streams. Let's explore some of these collectors:

counting()

The counting() collector counts the number of elements in a stream:

long count = students.stream()
                     .collect(Collectors.counting());

This code snippet counts the number of students.

summingInt(), summingLong(), summingDouble()

The summingInt(), summingLong(), and summingDouble() collectors calculate the sum of the specified primitive type from the stream elements. Here's an example that calculates the sum of student ages:

int totalAge = students.stream()
                       .collect(Collectors.summingInt(Student::getAge));

This code snippet calculates the total age of all students.

averagingInt(), averagingLong(), averagingDouble()

Similarly, the averagingInt(), averagingLong(), and averagingDouble() collectors calculate the average of the specified primitive type from the stream elements. Here's an example that calculates the average age of students:

double averageAge = students.stream()
                            .collect(Collectors.averagingInt(Student::getAge));

This code snippet calculates the average age of all students.

summarizingInt(), summarizingLong(), summarizingDouble()

The summarizingInt(), summarizingLong(), and summarizingDouble() collectors provide statistical summary about the stream elements. They return an instance of the IntSummaryStatistics, LongSummaryStatistics, and DoubleSummaryStatistics classes, respectively. Here's an example:

IntSummaryStatistics ageStatistics = students.stream()
                                              .collect(Collectors.summarizingInt(Student::getAge));

int minAge = ageStatistics.getMin();
int maxAge = ageStatistics.getMax();
double averageAge = ageStatistics.getAverage();
long count = ageStatistics.getCount();

This code snippet calculates various statistics about the student ages.

reducing()

The reducing() collector performs a reduction operation on the stream elements using a specified binary operator. Here's an example that calculates the maximum age of students:

OptionalInt maxAge = students.stream()
                             .mapToInt(Student::getAge)
                             .reduce(Integer::max);

This code snippet calculates the maximum age of all students.

Custom Collectors

In addition to the basic and aggregation collectors provided by the Collectors class, we can also create custom collectors to perform specialized operations. To create a custom collector, we need to implement the Collector interface.

Let's say we want to collect the names of students into a sorted list. We can create a custom collector as follows:

Collector<Student, ?, List<String>> sortedNamesCollector = Collector.of(
    ArrayList::new,                  // supplier
    (list, student) -> list.add(student.getName()),  // accumulator
    (list1, list2) -> {
        list1.addAll(list2);
        return list1;
    },                               // combiner
    Collections::sort                  // finisher