Java Stream的collect()本质是规约操作,自定义Collector需实现supplier、accumulator、combiner和finisher四个函数,并合理选择累加器类型A及设置Characteristics特性以确保正确性与性能。

Java Stream 的 collect() 操作本质是规约(reduction)——把流中元素逐步累积为一个结果。标准收集器(如 toList()、groupingBy())覆盖常见场景,但遇到定制结构、特殊合并逻辑或性能敏感场景时,必须自定义 Collector。
Collector 的四个核心行为
所有自定义 Collector 都围绕四个函数展开,它们共同定义了“如何收集”:
-
supplier:创建初始空容器(如
new ArrayList()或new int[1]),它是中间状态的起点; -
accumulator:将单个流元素“塞进”容器(如
list.add(e)或sum[0] += e),串行与并行流都调用它; -
combiner:把两个已部分累积的容器合并(如
(l1, l2) -> { l1.addAll(l2); return l1; }),仅在并行流中触发; -
finisher:对最终容器做收尾转换(如
list -> list.stream().sorted().collect(toList())),若中间容器类型与返回类型一致,可省略。
累加器类型(A)怎么选?
累加器 A 是中间状态的载体,选择直接影响简洁性与性能:
- 简单数值统计(求和、计数)→ 用
int[1]或AtomicInteger:避免装箱、线程安全、内存紧凑; - 需维护多个字段(如最大值+最小值+数量)→ 用匿名类或轻量级静态内部类,封装状态与逻辑;
- 构建 Map 或嵌套结构 → 直接用
HashMap或TreeMap作 A,accumulator 中执行 put/compute 等操作; - 字符串拼接 →
StringBuilder是经典选择,combiner 调用append()即可。
用 Collector.of() 快速实现
这是最常用、最推荐的方式,无需实现接口,直接传入四个函数:
立即学习“Java免费学习笔记(深入)”;
例如:统计字符串长度分布(Map<Integer, Long>)
Collector<String, Map<Integer, Long>, Map<Integer, Long>> lengthCount =
Collector.of(
HashMap::new,
(map, s) -> map.merge(s.length(), 1L, Long::sum),
(m1, m2) -> { m1.replaceAll((k, v) -> m2.getOrDefault(k, 0L) + v); return m1; },
Function.identity()
);
注意 combiner 中的合并逻辑要兼容并行语义——不能简单 m1.putAll(m2)(会覆盖),而应逐 key 合并计数。
特性(characteristics)别忽略
最后一个参数是 Set<Characteristics>,用于提示运行时优化:
-
CONCURRENT:表示 supplier 返回的容器本身线程安全(如ConcurrentHashMap::new),允许并行流跳过 combiner 阶段; -
UNORDERED:说明结果不依赖输入顺序(如toSet()),可启用更高效并行策略; -
IDENTITY_FINISH:当 finisher 是恒等函数(x -> x)时声明,避免无谓包装。
错误设置特性可能导致并发异常或性能下降,比如对 ArrayList 声明 CONCURRENT 就是危险的。


















