You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 17:47:03