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.
Collectors
produce mutable collections, such as ArrayList
or HashSet
, which can be modified after creation.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.
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()
.
Collectors.toList()
List
.List
).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]
}
}
Collectors.toSet()
Set
.equals()
.HashSet
is returned, so iteration order is unpredictable.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)
}
}
Collectors.toMap()
Creates a Map<K, V>
from stream elements.
Requires two functions:
By default, duplicate keys cause an IllegalStateException
.
To handle duplicate keys, provide a merge function to resolve conflicts.
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}
}
}
toList()
preserves order and allows duplicates.toSet()
removes duplicates but does not guarantee order.toMap()
requires key/value mappers and needs careful handling of duplicate keys via merge functions.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.
Collectors.joining()
— concatenates elements directly.Collectors.joining(delimiter)
— inserts a delimiter between elements.Collectors.joining(delimiter, prefix, suffix)
— adds delimiter, prefix, and suffix around the concatenated string.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.
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.
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.
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.
The summary statistics objects (IntSummaryStatistics
, DoubleSummaryStatistics
, and LongSummaryStatistics
) contain the following fields:
double
)These stats allow quick insights into the dataset without manually writing loops or multiple operations.
summarizingInt()
to Analyze Agesimport 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
summarizingDouble()
to Summarize Product Pricesimport 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
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.