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

Java Stream按多字段分组并实现多字段求和、求平均的方法

Java Stream 多字段分组与聚合操作问题解答

嘿,针对你提出的两个Java Stream分组聚合问题,我来一步步给你解答:


需求1:按name和code分组,统计account总数与time总和

要实现多字段分组+多维度聚合,核心要解决两个问题:构造复合分组键,以及同时计算多个聚合值。

解法1:Java 8兼容写法(无需额外依赖/语法特性)

首先,我们可以用Java自带的AbstractMap.SimpleEntry来做复合键(不用自定义类),再写一个简单的聚合结果类来存储统计值:

// 自定义聚合结果类,用来存account总数和time总和
class FooAggregateResult {
    private int totalAccount;
    private int totalTime;

    public FooAggregateResult(int totalAccount, int totalTime) {
        this.totalAccount = totalAccount;
        this.totalTime = totalTime;
    }

    // 累加单个Foo对象的数据
    public FooAggregateResult add(Foo foo) {
        this.totalAccount += foo.getAccount();
        this.totalTime += foo.getTime();
        return this;
    }

    // 合并两个聚合结果(支持并行流场景)
    public FooAggregateResult merge(FooAggregateResult other) {
        this.totalAccount += other.totalAccount;
        this.totalTime += other.totalTime;
        return this;
    }

    // Getters和toString方便查看结果
    public int getTotalAccount() { return totalAccount; }
    public int getTotalTime() { return totalTime; }
    @Override
    public String toString() {
        return "FooAggregateResult{totalAccount=" + totalAccount + ", totalTime=" + totalTime + "}";
    }
}

然后用Stream完成分组和聚合:

import java.util.AbstractMap;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

// 执行分组聚合
Map<AbstractMap.SimpleEntry<String, Integer>, FooAggregateResult> resultMap = fooList.stream()
        .collect(Collectors.groupingBy(
                // 复合分组键:把name和code打包成一个Entry
                foo -> new AbstractMap.SimpleEntry<>(foo.getName(), foo.getCode()),
                // 自定义收集器,实现累加与合并
                Collectors.collectingAndThen(
                        Collectors.reducing(
                                new FooAggregateResult(0, 0),
                                FooAggregateResult::add,
                                FooAggregateResult::merge
                        ),
                        Function.identity()
                )
        ));

解法2:Java 16+简洁写法(用Record简化代码)

如果你的项目已经用Java 16+,可以用Record来替代自定义类,代码会清爽很多:

// 用Record定义复合分组键
record FooGroupKey(String name, int code) {}
// 用Record定义聚合结果
record FooStats(int totalAccount, int totalTime) {}

// 分组聚合一行搞定
Map<FooGroupKey, FooStats> resultMap = fooList.stream()
        .collect(Collectors.groupingBy(
                foo -> new FooGroupKey(foo.getName(), foo.getCode()),
                // 用Collectors.teeing同时执行两个聚合,再合并结果
                Collectors.teeing(
                        Collectors.summingInt(Foo::getAccount),
                        Collectors.summingInt(Foo::getTime),
                        FooStats::new
                )
        ));

这里的teeing是Java 12引入的收集器,专门用来同时执行两个聚合逻辑,然后把结果合并,非常适合这种多维度统计的场景。


需求2:能否同时用summingInt(Foo::getAccount)和averagingInt(Foo::getTime)实现需求?

完全可以,甚至还有更高效的方式,分两种情况说:

情况1:account字段值恒为1(如你的示例)

这时候分组内的元素数量就等于account的总和,用Collectors.summarizingInt(Foo::getTime)就能一次性拿到count(对应totalAccount)、sum、average等所有统计值,比单独用两个收集器更高效:

Map<FooGroupKey, IntSummaryStatistics> resultMap = fooList.stream()
        .collect(Collectors.groupingBy(
                foo -> new FooGroupKey(foo.getName(), foo.getCode()),
                Collectors.summarizingInt(Foo::getTime)
        ));

// 后续获取结果:
// resultMap.get(key).getCount() → account总数(元素数量)
// resultMap.get(key).getAverage() → time的平均值

情况2:account字段值可能不为1

如果account的值可能变化,需要单独统计总和,就用teeing收集器同时执行两个聚合:

Map<FooGroupKey, Map<String, Object>> resultMap = fooList.stream()
        .collect(Collectors.groupingBy(
                foo -> new FooGroupKey(foo.getName(), foo.getCode()),
                Collectors.teeing(
                        Collectors.summingInt(Foo::getAccount),
                        Collectors.averagingInt(Foo::getTime),
                        // 把两个聚合结果打包成Map返回
                        (totalAccount, avgTime) -> Map.of(
                                "totalAccount", totalAccount,
                                "averageTime", avgTime
                        )
                )
        ));

内容的提问来源于stack exchange,提问作者San.s

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 21:02:44