Index

Collecting Stream Results

Java Streams

6.1 Introduction to Collectors

The Collector interface in Java Streams defines a strategy for reducing a stream of elements into a final container or result. It abstracts how elements are accumulated, combined, and finished to produce a desired output, such as a collection, a summary value, or a concatenated string.

The Collectors utility class provides a rich set of predefined collector implementations that cover common reduction scenarios like collecting to lists, sets, maps, joining strings, or computing summary statistics. This allows developers to write concise, readable code without manually implementing reduction logic.

Mutable vs Immutable Collections

Simple Example: Collecting to a List

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class CollectorIntroExample {
    public static void main(String[] args) {
        List<String> fruits = Stream.of("apple", "banana", "cherry")
                                    .collect(Collectors.toList());

        System.out.println(fruits);
        // Output: [apple, banana, cherry]
    }
}

In this example, the stream of fruit names is collected into a mutable List using the Collectors.toList() collector. This pattern is one of the most common ways to transform streams back into collections for further use.

By understanding the Collector interface and the power of built-in collectors, you unlock the full potential of stream processing and elegant data transformation in Java.

Index

6.2 Collecting to Lists, Sets, Maps

Java Streams provide powerful built-in collectors to accumulate stream elements into different collection types: lists, sets, and maps. These are available through the Collectors utility class as toList(), toSet(), and toMap().

Collecting to a List: Collectors.toList()

Example:

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class ToListExample {
    public static void main(String[] args) {
        List<String> fruits = Stream.of("apple", "banana", "cherry", "apple")
                                    .collect(Collectors.toList());

        System.out.println(fruits);
        // Output: [apple, banana, cherry, apple]
    }
}

Collecting to a Set: Collectors.toSet()

Example:

import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class ToSetExample {
    public static void main(String[] args) {
        Set<String> fruits = Stream.of("apple", "banana", "cherry", "apple")
                                  .collect(Collectors.toSet());

        System.out.println(fruits);
        // Output: [banana, cherry, apple] (order may vary)
    }
}

Collecting to a Map: Collectors.toMap()

Example 1: Simple Map without duplicates

import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class ToMapExample1 {
    public static void main(String[] args) {
        Map<String, Integer> nameLengths = Stream.of("apple", "banana", "cherry")
            .collect(Collectors.toMap(
                s -> s,        // key mapper: string itself
                String::length // value mapper: string length
            ));

        System.out.println(nameLengths);
        // Output: {apple=5, banana=6, cherry=6}
    }
}

Example 2: Handling duplicate keys with merge function

import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class ToMapExample2 {
    public static void main(String[] args) {
        Map<Character, String> initialsMap = Stream.of("apple", "banana", "apricot")
            .collect(Collectors.toMap(
                s -> s.charAt(0),  // key: first character
                s -> s,            // value: string itself
                (existing, replacement) -> existing + ", " + replacement // merge duplicates
            ));

        System.out.println(initialsMap);
        // Output: {a=apple, apricot, b=banana}
    }
}

Summary

Index

6.3 Joining Strings

The Collectors.joining() method is a convenient way to concatenate strings from a stream into a single result. It allows you to specify optional delimiters, prefixes, and suffixes, making it ideal for producing readable and well-formatted string outputs.

Basic Usage

Example 1: Simple Joining with Comma Delimiter

import java.util.stream.Collectors;
import java.util.stream.Stream;

public class JoiningExample1 {
    public static void main(String[] args) {
        String result = Stream.of("apple", "banana", "cherry")
                              .collect(Collectors.joining(", "));

        System.out.println(result);
        // Output: apple, banana, cherry
    }
}

This example joins stream elements into a comma-separated list, commonly used for display or CSV formatting.

Example 2: Joining with Prefix, Suffix, and Delimiter

import java.util.stream.Collectors;
import java.util.stream.Stream;

public class JoiningExample2 {
    public static void main(String[] args) {
        String result = Stream.of("Java", "Python", "C++")
                              .collect(Collectors.joining(" | ", "[Languages: ", "]"));

        System.out.println(result);
        // Output: [Languages: Java | Python | C++]
    }
}

Here, the joining operation produces a formatted string with a prefix and suffix, and uses " | " as the delimiter, making the output clearer and more descriptive.

Summary

Collectors.joining() is a simple yet powerful tool to convert streams of strings into a single formatted string. Its flexibility with delimiters, prefixes, and suffixes lets you customize output for user interfaces, logs, reports, or file exports with minimal code.

Index

6.4 Summary Statistics Collectors

Java Streams provide convenient built-in collectors such as Collectors.summarizingInt(), summarizingDouble(), and summarizingLong() that produce summary statistics objects. These objects encapsulate useful aggregate information about numeric data streams, enabling concise and efficient statistical calculations.

What Do These Collectors Provide?

The summary statistics objects (IntSummaryStatistics, DoubleSummaryStatistics, and LongSummaryStatistics) contain the following fields:

These stats allow quick insights into the dataset without manually writing loops or multiple operations.

Example 1: Using summarizingInt() to Analyze Ages

import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.stream.Collectors;

public class SummaryStatsExample1 {
    public static void main(String[] args) {
        List<Integer> ages = List.of(25, 30, 22, 40, 28);

        IntSummaryStatistics stats = ages.stream()
                                         .collect(Collectors.summarizingInt(Integer::intValue));

        System.out.println("Count: " + stats.getCount());
        System.out.println("Sum: " + stats.getSum());
        System.out.println("Min: " + stats.getMin());
        System.out.println("Max: " + stats.getMax());
        System.out.println("Average: " + stats.getAverage());
    }
}

Output:

Count: 5
Sum: 145
Min: 22
Max: 40
Average: 29.0

Example 2: Using summarizingDouble() to Summarize Product Prices

import java.util.DoubleSummaryStatistics;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class SummaryStatsExample2 {
    public static void main(String[] args) {
        DoubleSummaryStatistics priceStats = Stream.of(19.99, 9.99, 4.95, 29.99)
                                                   .collect(Collectors.summarizingDouble(Double::doubleValue));

        System.out.println("Count: " + priceStats.getCount());
        System.out.println("Sum: " + priceStats.getSum());
        System.out.println("Min: " + priceStats.getMin());
        System.out.println("Max: " + priceStats.getMax());
        System.out.println("Average: " + priceStats.getAverage());
    }
}

Output:

Count: 4
Sum: 64.92
Min: 4.95
Max: 29.99
Average: 16.23

Summary

Using summary statistics collectors dramatically simplifies the process of computing key numeric metrics. They provide a single, immutable object encapsulating multiple statistics, improving code clarity and reducing boilerplate when analyzing datasets in Java Streams.

Index