The Collector interface in Java, part of the java.util.stream package, defines how elements of a Stream are collected into a final result, such as a List, Set, Map, or other data structure. It’s used with the Stream.collect() method and is highly flexible, allowing both predefined collectors (via Collectors class) and custom implementations. Below, I’ll explain the Collector interface, its components, and how to use it.
Overview of the Collector Interface
The Collector<T, A, R> interface is generic with three type parameters:
- T: The type of input elements from the stream.
- A: The mutable accumulation type (intermediate result container, e.g., a List or StringBuilder).
- R: The final result type (e.g., List, Set, String).
The interface defines five methods that describe how accumulation works:
- supplier(): Creates a new mutable result container (A).
- accumulator(): Adds a single element (T) to the result container (A).
- combiner(): Merges two result containers (A) into one, used in parallel processing.
- finisher(): Transforms the intermediate result (A) into the final result (R).
- characteristics(): Returns a set of Characteristics that describe the collector’s behavior.
Method Details
Here’s the signature and purpose of each method:
public interface Collector<T, A, R> {
Supplier<A> supplier();
BiConsumer<A, T> accumulator();
BinaryOperator<A> combiner();
Function<A, R> finisher();
Set<Characteristics> characteristics();
}
Code language: HTML, XML (xml)
- Supplier<A> supplier(): Provides a new instance of the mutable container (A). For example, for a toList() collector, it returns a new ArrayList.
- BiConsumer<A, T> accumulator(): Defines how to add an element (T) to the container (A). For toList(), it appends the element to the list.
- BinaryOperator<A> combiner(): Specifies how to merge two containers (A) during parallel processing. For toList(), it combines two lists into one.
- Function<A, R> finisher(): Converts the intermediate container (A) to the final result (R). For toList(), it’s often an identity function since A and R are both List.
- Set<Characteristics> characteristics(): Returns a set of flags (Characteristics) that describe the collector’s properties:
-
- CONCURRENT: The collector supports concurrent accumulation into a shared container.
- UNORDERED: The collector doesn’t preserve the encounter order of elements.
- IDENTITY_FINISH: The finisher is the identity function (i.e., A is already R, so no transformation is needed).
Using Predefined Collectors
The Collectors class provides many built-in collectors for common tasks. Examples:
- Collectors.toList(): Collects elements into a List.
- Collectors.toSet(): Collects elements into a Set.
- Collectors.toMap(keyMapper, valueMapper): Collects elements into a Map.
- Collectors.joining(delimiter): Concatenates strings with a delimiter.
Example:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<String> result = names.stream()
.collect(Collectors.toList());
// Result: [Alice, Bob, Charlie]
Code language: JavaScript (javascript)
Creating a Custom Collector
You can implement the Collector interface for custom collection logic. Here’s an example that collects elements into a StringBuilder and returns a String:
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
public class CustomCollector implements Collector<String, StringBuilder, String> {
@Override
public Supplier<StringBuilder> supplier() {
return StringBuilder::new; // Create a new StringBuilder
}
@Override
public BiConsumer<StringBuilder, String> accumulator() {
return StringBuilder::append; // Append each string to StringBuilder
}
@Override
public BinaryOperator<StringBuilder> combiner() {
return (sb1, sb2) -> sb1.append(sb2); // Merge two StringBuilders
}
@Override
public Function<StringBuilder, String> finisher() {
return StringBuilder::toString; // Convert StringBuilder to String
}
@Override
public Set<Characteristics> characteristics() {
return Set.of(); // No special characteristics
}
}
Code language: PHP (php)
Usage:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
String result = names.stream()
.collect(new CustomCollector());
// Result: "AliceBobCharlie"
Code language: JavaScript (javascript)
Collector<String, StringBuilder, String> collector = Collector.of(
StringBuilder::new, // supplier
StringBuilder::append, // accumulator
(sb1, sb2) -> sb1.append(sb2), // combiner
StringBuilder::toString // finisher
);
String result = names.stream().collect(collector);
// Result: "AliceBobCharlie"
Code language: JavaScript (javascript)
Collector<String, StringBuilder, String> collector = Collector.of(
StringBuilder::new, // supplier
StringBuilder::append, // accumulator
(sb1, sb2) -> sb1.append(sb2), // combiner
StringBuilder::toString // finisher
);
String result = names.stream().collect(collector);
// Result: "AliceBobCharlie"
Code language: JavaScript (javascript)
Characteristics in Detail
The characteristics() method influences how the stream processes the collector:
- CONCURRENT: Allows multiple threads to accumulate into a shared container. Requires a thread-safe container (e.g., ConcurrentHashMap).
- UNORDERED: Indicates the result doesn’t depend on the order of elements (e.g., toSet()). Enables optimizations in parallel processing.
- IDENTITY_FINISH: Signals that the finisher is an identity function, so the intermediate result (A) can be cast directly to the final result (R). Common in toList() or toSet().
Example with characteristics:
Collector<String, List<String>, List<String>> listCollector = Collector.of(
ArrayList::new,
List::add,
(list1, list2) -> { list1.addAll(list2); return list1; },
Function.identity(),
Collector.Characteristics.IDENTITY_FINISH
);
Code language: PHP (php)
The Collector interface in Java Streams is a versatile tool for defining custom reduction operations, enabling sophisticated data collection and transformation with ease.