自定义 Collector 的核心是明确分段累计与结果合并两阶段行为,需正确实现 Supplier、accumulator、combiner、finisher 及 characteristics;推荐用 Collector.of() 构建无状态收集器,或实现完整接口封装有状态逻辑。

Java Stream API 中通过自定义 Collector 实现特殊收集逻辑,核心是实现 Collector 接口的五个方法(或使用 Collector.of() 静态工厂),关键在于明确「分段累计」和「结果合并」两个阶段的行为。
理解 Collector 的三个核心类型参数
Collector<t a r></t> 中:
- T:流中每个元素的类型(输入类型)
-
A:可变的中间累积器类型(如
StringBuilder、HashMap<K, List<V>>、自定义容器类) -
R:最终收集结果类型(可与 A 相同,也可不同,比如 A 是
Map<K, List<V>>,R 是Map<K, Set<V>>)
用 Collector.of() 快速构建无状态 Collector
适合不需要共享状态、纯函数式逻辑的场景,例如:按首字母分组并转为大写字符串列表
Collector<String, ?, Map<Character, List<String>>> collector = Collector.of(
LinkedHashMap::new, // supplier:新建空容器
(map, str) -> {
char first = Character.toUpperCase(str.charAt(0));
map.computeIfAbsent(first, k -> new ArrayList<>()).add(str.toUpperCase());
}, // accumulator:累加单个元素
(map1, map2) -> {
map2.forEach((k, v) -> map1.merge(k, v, (l1, l2) -> { l1.addAll(l2); return l1; }));
return map1;
}, // combiner:合并两个中间容器(并行流必需)
Function.identity(), // finisher:可选转换(这里不需要,直接返回 map)
Collector.Characteristics.IDENTITY_FINISH // 标识该 Collector 不做 finisher 转换
);
注意:combiner 在并行流中必须正确处理并发合并;若逻辑天然不可分(如需要全局排序),应避免并行或改用线程安全容器。
立即学习“Java免费学习笔记(深入)”;
实现完整 Collector 接口(适合复杂有状态逻辑)
当需要封装状态(如统计最大值+对应元素、维护滑动窗口、带阈值去重)时,推荐定义一个私有静态内部类:
static class MaxByLengthCollector implements Collector<String, MaxHolder, String> {
static class MaxHolder {
String max = "";
int maxLength = 0;
}
@Override
public Supplier<MaxHolder> supplier() {
return MaxHolder::new;
}
@Override
public BiConsumer<MaxHolder, String> accumulator() {
return (holder, s) -> {
if (s.length() > holder.maxLength) {
holder.max = s;
holder.maxLength = s.length();
}
};
}
@Override
public BinaryOperator<MaxHolder> combiner() {
return (h1, h2) -> {
if (h2.maxLength > h1.maxLength) {
h1.max = h2.max;
h1.maxLength = h2.maxLength;
}
return h1;
};
}
@Override
public Function<MaxHolder, String> finisher() {
return holder -> holder.max;
}
@Override
public Set<Characteristics> characteristics() {
return Set.of(Characteristics.UNORDERED); // 因为只关心最大长度,不依赖顺序
}
}
使用:stream.collect(new MaxByLengthCollector())。这种方式更清晰、可复用、便于单元测试。
常见易错点与建议
-
combiner 必须幂等且满足结合律:确保
combiner.apply(combiner.apply(a,b),c)等价于combiner.apply(a, combiner.apply(b,c)),否则并行结果不可靠 -
不要在 accumulator 中修改上游元素:Stream 元素应视为不可变,避免副作用(如调用
list.clear()) - finisher 应尽量轻量:若需昂贵计算(如排序、格式化),考虑是否应在 finisher 外显式调用,保持 Collector 职责单一
-
合理声明 Characteristics:如确定不依赖顺序,加
UNORDERED可提升并行性能;若容器本身线程安全(如ConcurrentHashMap),可加CONCURRENT(此时 supplier 必须返回同一实例,且 combiner 可简化)
自定义 Collector 不是语法糖,而是把「如何累积」和「如何合并」显式建模——理清这两步,复杂收集逻辑就变得可控且可组合。


















