如何在Collectors.groupingBy中按枚举动态应用自定义聚合?
解决方案
要实现根据A中的AggregationType动态选择聚合逻辑,核心思路是将聚合行为与枚举值绑定,再结合分组后的结果执行对应逻辑。以下提供两种实用方案:
方案一:枚举绑定Collector(直接复用JDK收集器)
首先改造AggregationType枚举,为每个枚举值关联对应的Collector,这样可以直接通过枚举获取聚合所需的收集器:
enum AggregationType { SUM(Collectors.summingDouble(entry -> (double) ((Map.Entry<A, B>) entry).getValue().getValue())), AVG(Collectors.averagingDouble(entry -> (double) ((Map.Entry<A, B>) entry).getValue().getValue())), MIN(Collectors.minBy(Comparator.comparingDouble(entry -> (double) ((Map.Entry<A, B>) entry).getValue().getValue()))), MAX(Collectors.maxBy(Comparator.comparingDouble(entry -> (double) ((Map.Entry<A, B>) entry).getValue().getValue()))); private final Collector<Map.Entry<A, B>, ?, ?> collector; AggregationType(Collector<Map.Entry<A, B>, ?, ?> collector) { this.collector = collector; } @SuppressWarnings("unchecked") public <R> Collector<Map.Entry<A, B>, ?, R> getCollector() { return (Collector<Map.Entry<A, B>, ?, R>) collector; } }
然后按A.id分组后,对每个分组应用对应聚合逻辑:
// 1. 先按ID分组,得到每个ID对应的所有条目 Map<Long, List<Map.Entry<A, B>>> groupedById = AvsB.entrySet().stream() .collect(Collectors.groupingBy(entry -> entry.getKey().getId())); // 2. 处理每个分组,执行对应聚合 Map<Long, Object> aggregatedMap = new HashMap<>(); for (Map.Entry<Long, List<Map.Entry<A, B>>> group : groupedById.entrySet()) { Long id = group.getKey(); List<Map.Entry<A, B>> entries = group.getValue(); // 假设同一ID下所有A的聚合类型一致,取第一个条目聚合类型 AggregationType aggrType = entries.get(0).getKey().getAggr(); Collector<Map.Entry<A, B>, ?, ?> collector = aggrType.getCollector(); Object result = entries.stream().collect(collector); // 处理MIN/MAX返回的Optional,转为实际值(可根据业务调整默认值) if (result instanceof Optional) { result = ((Optional<?>) result).orElse(null); } aggregatedMap.put(id, result); }
方案二:枚举定义聚合函数(类型统一更灵活)
如果希望聚合结果类型统一为Double,可以在枚举中直接定义聚合逻辑,避免处理Object和Optional的麻烦:
enum AggregationType { SUM { @Override public Double aggregate(List<Long> values) { return values.stream().mapToDouble(Long::doubleValue).sum(); } }, AVG { @Override public Double aggregate(List<Long> values) { return values.stream().mapToDouble(Long::doubleValue).average().orElse(Double.NaN); } }, MIN { @Override public Double aggregate(List<Long> values) { return values.stream().mapToDouble(Long::doubleValue).min().orElse(Double.NaN); } }, MAX { @Override public Double aggregate(List<Long> values) { return values.stream().mapToDouble(Long::doubleValue).max().orElse(Double.NaN); } }; // 定义聚合抽象方法 public abstract Double aggregate(List<Long> values); }
然后通过自定义Collector完成分组+聚合的一站式操作:
Map<Long, Double> aggregatedMap = AvsB.entrySet().stream() .collect(Collectors.groupingBy( entry -> entry.getKey().getId(), Collector.of( // 初始化容器:存储聚合类型与对应值列表 () -> new HashMap<AggregationType, List<Long>>(), // 累加逻辑:将每个值存入对应聚合类型的列表 (acc, entry) -> { AggregationType aggr = entry.getKey().getAggr(); acc.computeIfAbsent(aggr, k -> new ArrayList<>()) .add(entry.getValue().getValue()); }, // 合并逻辑:合并两个容器的列表 (acc1, acc2) -> { acc2.forEach((k, v) -> acc1.merge(k, v, (list1, list2) -> { list1.addAll(list2); return list1; })); return acc1; }, // 最终转换:执行聚合逻辑,若同一ID存在多聚合类型则抛出异常 acc -> { if (acc.size() != 1) { throw new IllegalArgumentException("同一ID下存在多种冲突的聚合类型"); } Map.Entry<AggregationType, List<Long>> entry = acc.entrySet().iterator().next(); return entry.getKey().aggregate(entry.getValue()); } ) ));
注意事项
- 若业务中存在同一
A.id对应不同AggregationType的场景,需在代码中添加冲突处理逻辑(如抛出异常、按优先级选择聚合类型等)。 - 方案二返回统一的
Double类型,更便于后续业务处理;方案一则保留了JDK收集器的原生返回类型(如Optional),灵活性更高。
内容的提问来源于stack exchange,提问作者Aravindh Vasu
相关产品推荐
相关产品推荐

