构建自定义收集器
实现 Collector 接口,为特定领域的使用场景创建可复用的聚合器
构建自定义收集器 是 CoddyKit 上的免费 Java Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Java Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Java Academy 课程共包含 4 节课。
为什么要构建自定义收集器?
当内置收集器无法满足您的业务领域需求时,可以实现 Collector<T, A, R> 接口,创建可复用、可组合的聚合逻辑。T=输入,A=可变累加器,R=结果。
收集器接口
收集器包含五个组成部分:
supplier()——创建初始累加器accumulator()——将一个元素折叠到累加器中combiner()——合并两个累加器(用于并行流)finisher()——将累加器转换为最终结果characteristics()——提供优化提示
Collector.of() 工厂方法
创建自定义收集器最简单的方式,是使用 Collector.of(),无需直接实现接口:
import java.util.stream.*;
// Collector that builds a comma-separated String
Collector<String, StringBuilder, String> csvCollector = Collector.of(
StringBuilder::new, // supplier
(sb, s) -> {
if (sb.length() > 0) sb.append(',');
sb.append(s);
}, // accumulator
(a, b) -> {
if (a.length() > 0 && b.length() > 0) a.append(',');
a.append(b); return a;
}, // combiner
StringBuilder::toString // finisher
);
String csv = Stream.of("Alice","Bob","Carol").collect(csvCollector);
System.out.println(csv); // Alice,Bob,Carol自定义收集器:前 N 项
构建一个仅按比较器保留前 N 个元素的收集器:
import java.util.*;
import java.util.stream.*;
static <T> Collector<T, ?, List<T>> topN(int n, Comparator<T> comp) {
return Collector.of(
() -> new ArrayList<T>(),
(list, e) -> {
list.add(e);
list.sort(comp.reversed());
if (list.size() > n) list.remove(list.size() - 1);
},
(a, b) -> {
a.addAll(b);
a.sort(comp.reversed());
while (a.size() > n) a.remove(a.size() - 1);
return a;
},
Collector.Characteristics.UNORDERED
);
}使用前 N 项收集器
在流上应用自定义的前 N 项收集器:
List<Integer> top3 = Stream.of(5,2,9,1,7,3,8)
.collect(topN(3, Comparator.naturalOrder()));
System.out.println(top3); // [9, 8, 7]特性标志
三个可选特性可用于调整流的优化:
IDENTITY_FINISH——完成器是恒等变换;跳过完成器UNORDERED——结果顺序无关紧要CONCURRENT——可以并发调用累加器
设置错误的标志(例如在不具备线程安全性的情况下设置 CONCURRENT)会导致错误。
自定义收集器:运行平均值
无需进行第二次遍历即可维护运行平均值:
record RunningAvg(long count, double sum) {
RunningAvg add(double v) { return new RunningAvg(count+1, sum+v); }
RunningAvg merge(RunningAvg o) { return new RunningAvg(count+o.count, sum+o.sum); }
double avg() { return count == 0 ? 0 : sum / count; }
}
Collector<Double, RunningAvg[], Double> avgCollector = Collector.of(
() -> new RunningAvg[]{new RunningAvg(0, 0)},
(a, v) -> a[0] = a[0].add(v),
(a, b) -> { a[0] = a[0].merge(b[0]); return a; },
a -> a[0].avg()
);
double avg = Stream.of(10.0, 20.0, 30.0).collect(avgCollector);
System.out.println(avg); // 20.0直接实现收集器接口
如需更多控制,可以将 Collector<T,A,R> 实现为一个类:
import java.util.function.*;
import java.util.stream.*;
class StringJoiningCollector implements Collector<String, List<String>, String> {
private final String delimiter;
StringJoiningCollector(String delimiter) { this.delimiter = delimiter; }
public Supplier<List<String>> supplier() { return ArrayList::new; }
public BiConsumer<List<String>, String> accumulator() { return List::add; }
public BinaryOperator<List<String>> combiner() {
return (a,b) -> { a.addAll(b); return a; };
}
public Function<List<String>, String> finisher() {
return list -> String.join(delimiter, list);
}
public Set<Characteristics> characteristics() { return Set.of(); }
}自定义收集器与内置收集器的比较
在编写自定义收集器之前,请先检查组合内置收集器是否可以达到相同结果——内置收集器已经过测试和优化:
// Instead of custom, compose:
Map<String, IntSummaryStatistics> stats = orders.stream().collect(
Collectors.groupingBy(Order::status,
Collectors.summarizingInt(Order::amount))
);
// This replaces many custom collector implementations自定义收集器中的线程安全
如果设置了 CONCURRENT 特性,多个线程会同时调用累加器。因此,累加器必须是线程安全的(例如使用 ConcurrentHashMap 或 AtomicLong)。如果没有设置 CONCURRENT,则由组合器安全地处理并行操作。
实际应用:直方图收集器
将数值分桶到直方图映射中:
Collector<Integer, Map<Integer, Long>, Map<Integer, Long>> histogram =
Collector.of(
LinkedHashMap::new,
(map, val) -> map.merge(val / 10 * 10, 1L, Long::sum),
(a, b) -> { b.forEach((k,v) -> a.merge(k,v,Long::sum)); return a; },
Collector.Characteristics.IDENTITY_FINISH
);
Map<Integer,Long> hist = Stream.of(5,15,25,12,8,22,18,3)
.collect(histogram);
hist.forEach((bucket, cnt) -> System.out.println(bucket+"-"+(bucket+9)+": "+cnt));快速检查
哪个收集器特性标志表示完成函数是恒等变换,因此可以跳过?
回顾:自定义收集器
要点回顾:
- 收集器<T,A,R>:T=输入,A=累加器,R=结果
- 使用 Collector.of(),无需创建类即可快速实现
- 供应器、累加器、组合器、完成器和特性是五个组成部分
- 身份完成、UNORDERED、CONCURRENT 是优化标志
- 优先组合内置收集器;只有在必要时才自定义
常见问题解答
「构建自定义收集器」课时是免费的吗?
是的 — 「构建自定义收集器」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Java Academy 课程的其余内容,请升级到 CoddyKit PRO。 Java Academy 课程共包含 4 节课。
「构建自定义收集器」这节课中我会学到什么?
实现 Collector 接口,为特定领域的使用场景创建可复用的聚合器 你通过在浏览器中直接运行的动手代码来练习 Java Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Java Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Java Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「构建自定义收集器」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Java Academy 课中编写并运行代码吗?
能。每节 Java Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。