Java接口Collector详解
Collector<T, A, R> 是 Java Stream API 的核心接口之一,用于定义可变的归约操作(如 collect() 方法中的操作)。它的核心作用是将流中的元素累积到可变容器中,并可选择对结果进行最终转换。
原理简介
接口定义:
public interface Collector<T, A, R> {
Supplier<A> supplier();
BiConsumer<A, T> accumulator();
BinaryOperator<A> combiner();
Function<A, R> finisher();
Set<Characteristics> characteristics();
}
泛型参数含义:
- T:流中元素的类型(输入类型)。
- A:累加器的类型(中间可变容器的类型,如
ArrayList)。 - R:最终结果的类型(如
List<T>或String)。
必须实现的4个核心方法:
Supplier<A> supplier(): 返回一个空的累加器实例(如ArrayList::new)。BiConsumer<A, T> accumulator(): 定义如何将元素合并到累加器(如List::add)。BinaryOperator<A> combiner(): 合并两个并行计算的累加器(用于并行流,如(list1, list2) -> { list1.addAll(list2); return list1; })。Function<A, R> finisher(): 将累加器转换为最终结果(如Function.identity()表示直接返回累加器本身)。
可选方法:
Set<Characteristics> characteristics(): 返回特征集合(如CONCURRENT,UNORDERED,IDENTITY_FINISH),用于优化性能。
要理解上面这些方法,需要去查看Supplier、BiConsumer、BinaryOperator、Function的定义。以下是每个方法的详细解析和对应的函数式接口说明:
1.Supplier<A> supplier()
@FunctionalInterface
public interface Supplier<T> {
T get(); // 无参数,返回一个结果
}
作用: 创建初始容器:在流处理开始时,提供一个空的累加器实例(类型为 A)。
2.BiConsumer<A, T> accumulator()
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u); // 接受两个参数,无返回值
}
作用: 元素累积:定义如何将流中的元素(类型 T)合并到累加器(类型 A)中。
3.BinaryOperator<A> combiner()
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T, T, T> {
T apply(T t1, T t2); // 接受两个同类型参数,返回同类型结果
}
作用: 并行合并:在并行流中,将两个部分结果(类型 A)合并为一个。
4.Function<A, R> finisher()
@FunctionalInterface
public interface Function<T, R> {
R apply(T t); // 接受一个参数,返回转换后的结果
}
作用:最终转换:将累加器(类型 A)转换为最终结果(类型 R);恒等函数:如果 A 和 R 相同,可直接用 Function.identity(),直接返回累加器本身。下面是Function.identity()的定义:
static <T> Function<T, T> identity() {
return t -> t;
}
简化实现方式:
通常不需要手动实现,而是通过 Collectors 工具类直接使用预定义的收集器(如 toList(), joining())。 若需自定义,可用 Collector.of() 快速创建:
// Collectors.toList() 的简化实现
List<Integer> list = Stream.of(1, 2, 3)
.collect(Collector.of(
ArrayList::new, // supplier
List::add, // accumulator
(left, right) -> { // combiner
left.addAll(right);
return left;
},
Characteristics.IDENTITY // 无需finisher(直接返回累加器)
));
典型用途:
List<String> list = stream.collect(Collectors.toList()); // 内置收集器
理解这个接口后,可以灵活实现自定义的聚合操作(如复杂统计、分组等)。
Collectors工具类
java.util.stream.Collectors是一个工厂类,提供了大量预定义的Collector实现。这些静态方法覆盖了90%以上的收集需求,让我们无需手动实现Collector接口。
常用Collectors方法速查表
| 方法 | 功能 | 示例 |
|---|---|---|
toList() |
收集到List | collect(Collectors.toList()) |
toSet() |
收集到Set | collect(Collectors.toSet()) |
toMap() |
收集到Map | collect(toMap(k, v)) |
joining() |
连接字符串 | collect(joining(", ")) |
groupingBy() |
分组收集 | collect(groupingBy(User::getCity)) |
partitioningBy() |
分区收集 | collect(partitioningBy(u -> u.getAge() > 18)) |
summingInt() |
整数求和 | collect(summingInt(Product::getPrice)) |
averagingDouble() |
求平均值 | collect(averagingDouble(Student::getScore)) |
实际应用示例
1. 基础收集:转List/Set
List<String> names = employees.stream()
.map(Employee::getName)
.collect(Collectors.toList());
Set<String> uniqueNames = employees.stream()
.map(Employee::getName)
.collect(Collectors.toSet());
2. 分组收集:groupingBy()
// 按部门分组
Map<Department, List<Employee>> byDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
// 多级分组:先按部门,再按职位
Map<Department, Map<Position, List<Employee>>> multiLevel = employees.stream()
.collect(groupingBy(Employee::getDepartment,
groupingBy(Employee::getPosition)));
3. 分区收集:partitioningBy()
// 将员工分为管理者与非管理者
Map<Boolean, List<Employee>> partitioned = employees.stream()
.collect(Collectors.partitioningBy(e -> e.isManager()));
4. 统计操作
// 计算平均工资
double avgSalary = employees.stream()
.collect(Collectors.averagingDouble(Employee::getSalary));
// 获取工资汇总统计
DoubleSummaryStatistics stats = employees.stream()
.collect(Collectors.summarizingDouble(Employee::getSalary));
// stats: count, sum, min, max, average
5. 自定义收集器
当内置收集器不满足需求时,可以自定义Collector:
1.自定义字符串连接收集器
Collector<String, StringBuilder, String> concatCollector = Collector.of(
StringBuilder::new, // supplier
StringBuilder::append, // accumulator
(sb1, sb2) -> sb1.append(sb2), // combiner
StringBuilder::toString, // finisher
Characteristics.IDENTITY_FINISH
);
2.统计平均值
Collector<Integer, int[], Double> avgCollector = Collector.of(
() -> new int[2], // [0] = sum, [1] = count
(a, i) -> { a[0] += i; a[1]++; },
(a1, a2) -> {
a1[0] += a2[0];
a1[1] += a2[1];
return a1;
},
a -> (double) a[0] / a[1]
);
Collector执行流程解析
理解Collector如何工作:
-
创建容器:调用
supplier()创建新容器 -
累积元素:对每个元素调用
accumulator() -
合并结果(并行流):使用
combiner()合并部分结果 -
最终转换:通过
finisher()转换为最终形式
下面是个示例:
// 自定义字符串拼接收集器
Collector<String, StringBuilder, String> concatCollector = Collector.of(
StringBuilder::new, // supplier: 创建空StringBuilder
(sb, s) -> sb.append(s), // accumulator: 逐个拼接字符串
(sb1, sb2) -> sb1.append(sb2), // combiner: 合并两个StringBuilder
StringBuilder::toString // finisher: 转为String
);
// 使用示例
String result = Stream.of("a", "b", "c", "d").collect(concatCollector);
System.out.println(result); // 输出 "abcd"
流程拆解(以并行流为例): 假设输入流为 ["a", "b", "c", "d"],并行度为 2:
- 分区处理:
线程1:supplier()→ 新建StringBuilder sb1,处理["a", "b"]→sb1 = "ab"
线程2:supplier()→ 新建StringBuilder sb2,处理["c", "d"]→sb2 = "cd" - 合并结果:
combiner()被调用 →sb1.append(sb2)→sb1 = "abcd" - 最终转换:
finisher()被调用 →sb1.toString()→ 返回"abcd"
核心方法协作流程表格汇总:
| 阶段 | 方法 | 顺序流调用次数 | 并行流调用次数 |
|---|---|---|---|
| 初始化 | supplier() |
1 次 | 每个线程 1 次 |
| 累积 | accumulator() |
每个元素 1 次 | 每个元素在各自线程 1 次 |
| 合并 | combiner() |
0 次 | 线程数-1 次(合并子结果) |
| 转换 | finisher() |
1 次(除非有标识特性) | 1 次 |
characteristics() 方法详解
Collector 接口中的 characteristics() 方法通过返回一组 Characteristics 枚举值来声明收集器的行为特性,这些特性会直接影响 并行流(Parallel Stream) 的执行效率和正确性。以下是三种特性对性能优化的具体影响:
1.CONCURRENT(并发性)
作用:表示累加器(accumulator)支持多线程并发操作同一个容器。
优化场景:
- 并行流中,所有线程共享同一个累加器容器(而非每个线程创建自己的容器),避免了
combiner的合并开销。 - 适用于线程安全的容器(如 ConcurrentHashMap)。
风险:
- 如果容器非线程安全(如 ArrayList),会导致数据竞争(需手动同步)。
示例:
// 使用线程安全的容器(如 ConcurrentHashMap)
Collector.of(
ConcurrentHashMap::new,
(map, element) -> map.put(element, 1),
(map1, map2) -> { map1.putAll(map2); return map1; },
Characteristics.CONCURRENT
);
2.UNORDERED(无序性)
作用:表示结果不依赖元素的处理顺序。
优化场景:
- 并行流可以忽略元素的原始顺序,减少排序开销(如 HashSet 的收集比 LinkedHashSet 更快)。
- 与 CONCURRENT 结合时,能进一步减少线程间的协调开销。
示例:
// 收集到 HashSet(无序)比 LinkedHashSet(有序)更高效
Collector.of(
HashSet::new,
Set::add,
(set1, set2) -> { set1.addAll(set2); return set1; },
Characteristics.UNORDERED
);
3.IDENTITY_FINISH(恒等完成)
作用:表示 finisher() 是恒等函数(即最终结果 R 就是累加器 A)。
优化场景:
- 跳过
finisher()的调用,减少一次转换开销。 - 适用于 A 和 R 类型相同的场景(如 toList() 直接返回 ArrayList)。
示例:
// toList() 的简化实现:无需 finisher()
Collector.of(
ArrayList::new,
List::add,
(list1, list2) -> { list1.addAll(list2); return list1; },
Characteristics.IDENTITY_FINISH
);
通过合理声明 characteristics(),可以显著提升并行流的吞吐量,但需确保特性与实际行为一致。
欢迎关注公众号"飞鸿影记(fhyblog)",探寻物件背后的逻辑,记录生活真实的影子。

作者:飞鸿影
出处:http://52fhy.cnblogs.com/
版权申明:没有标明转载或特殊申明均为作者原创。本文采用以下协议进行授权,自由转载 - 非商用 - 非衍生 - 保持署名 | Creative Commons BY-NC-ND 3.0,转载请注明作者及出处。


浙公网安备 33010602011771号