Java Stream API自定义收集器Collector实现步骤详解
自定义收集器需提供供给器、累加器、组合器、终结器四个组件及特征声明。先明确收集目标与中间容器,再实现核心函数,注意并行合并的幂等性。特征标记可选并发、无序、恒等终结,最后通过串并行流验证。
要实现一个自定义的 Collector,本质上就是提供四个关键组件:supplier(容器怎么建)、accumulator(元素怎么往里塞)、combiner(并行时怎么合并)、finisher(最后怎么转成想要的结果),再顺手声明一下特征(characteristics)就完事了。听起来步骤清晰,但真上手写的时候,细节上容易踩坑。

1. 明确收集目标与中间容器
第一步,得先想清楚你到底要收集成什么结果。比如要去重后拼个字符串?还是分组统计?或者构建一个自定义对象?目标不同,中间容器的类型就跟着变。举个实际例子,如果你要做不重复的字符串拼接,中间容器用 LinkedHashSet 就很合适——既能保证顺序,又能天然去重。要是想统计频次,HashMap 就是标配。
- 这个中间容器不一定是最终返回的类型,它只是方便你在累积过程中操作。
- 容器最好选能在单线程下高效工作的类型;并行流里合并靠的是 combiner,别在 accumulator 里瞎做跨线程操作——否则很容易搞出并发问题。
2. 实现四个核心函数
通过 Collector.of() 这个静态工厂方法,把四个函数依次传进去就行了:
- supplier:比如
() -> new LinkedHashSet<>(),每次调用都新建一个空容器。 - accumulator:比如
(set, item) -> set.add(item),把流里的每个元素塞进容器里。 - combiner:比如
(set1, set2) -> { set1.addAll(set2); return set1; },合并两个中间容器——注意只有并行流才会用到这个方法。 - finisher:比如
set -> String.join(",", set),把最终的容器转成你想要的结果类型。
有一个小窍门:如果 finisher 其实是恒等函数(比如 Function.identity()),那直接省略就行,此时 Collector 返回的就是中间容器本身。
3. 正确声明 Characteristics(特征)
最后一个参数是一个 Set,里面可以选几个常见的特征标记:
CONCURRENT:表示 accumulator 支持多线程安全调用(比如你用了ConcurrentHashMap),此时 supplier 必须返回同一个共享实例——但坦率讲,一般不建议这么搞,容易翻车。UNORDERED:表示收集结果不依赖流中元素的原始顺序(比如统计总数、求最大值),开启这个标记可以提升并行性能。IDENTITY_FINISH:表示 finisher 是恒等操作,框架可以跳过转换步骤,少一次函数调用。
大多数自定义 Collector 不会设 CONCURRENT,而是老老实实靠 combiner 来合并——这种做法更安全、也更通用。
4. 使用与验证
写完之后,直接在 stream 后面调用 collect(yourCollector) 就能用了。不过建议你至少做两轮测试:
- 串行流:验证逻辑对不对,比如去重是否成功、拼接顺序是否合理。
- 并行流:重点验证 combiner 是否是幂等的,而且没有副作用——比如你写
list1.addAll(list2); return list2;,那结果就会把一部分数据弄丢,这种低级错误很容易犯。 - 另外还得检查一下:有没有意外修改了原始数据?典型的坑是在 accumulator 里直接修改传入的入参对象,后果就是流里的元素被你改了。
整体来说,自定义 Collector 算不上复杂,但每个环节的细节都值得花点心思,尤其是并行场景下的合并逻辑。


































