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

Java 8中如何用GroupBy实现计数?求分组统计状态的更优代码

Java 8 GroupBy 计数相关问题解答

问题1:如何在Java 8中使用GroupBy函数实现计数?

其实在Java 8的Stream API里,用Collectors.groupingBy()搭配Collectors.counting()就能轻松搞定分组计数的需求。简单来说,groupingBy()负责按你指定的规则(比如对象的某个属性)把元素分成不同的组,counting()作为“下游收集器”,专门统计每个组里的元素数量。

举个直观的例子:假设咱们有一堆水果名称,要统计每种水果出现的次数:

List<String> fruits = Arrays.asList("apple", "banana", "apple", "orange", "banana", "apple");
Map<String, Long> fruitCount = fruits.stream()
    .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

// 输出结果就是 {apple=3, banana=2, orange=1}
fruitCount.forEach((fruit, count) -> System.out.println(fruit + ": " + count));

如果是自定义对象,比如你代码里的Model,要按name属性分组计数,写法也类似:

List<Model> models = new ArrayList<>(test.values());
Map<String, Long> nameCount = models.stream()
    .collect(Collectors.groupingBy(Model::getName, Collectors.counting()));

这样就能快速得到每个name对应的订单总数啦。


问题2:如何结合GroupBy与计数函数处理多状态统计需求?

你的需求是按股票名称分组,统计总订单数以及INPROGRESS、CANCEL、COMPLETED三种状态各自的数量。你原来的实现虽然能跑通,但多次遍历数据流,不仅效率不高,代码也有点冗余。咱们来看看更规范、高效的实现方式:

先提个小建议:拆分职责

你的Model类既存了订单的基础信息(name、status),又存了统计数据(total、各种状态计数),有点违反单一职责原则。建议单独创建一个统计用的DTO,让每个类只干一件事:

// 专门用来存统计结果的类
class StockStatusStats {
    private String stockName;
    private long total;
    private long inProgress;
    private long cancel;
    private long completed;

    // 记得添加getter和setter方法
    // 自定义toString方法,方便输出格式对齐
    @Override
    public String toString() {
        return String.format("%s | %d | %d | %d | %d", 
            stockName, total, inProgress, cancel, completed);
    }
}

方案1:嵌套GroupBy + 转换统计对象

这种方法只需要遍历一次数据流,先按股票名称分组,每个组内再按状态分组计数,最后把状态计数的结果转换成我们需要的统计对象:

Map<String, StockStatusStats> statsMap = test.values().stream()
    .collect(Collectors.groupingBy(
        Model::getName,
        Collectors.collectingAndThen(
            // 第一步:组内按状态分组计数
            Collectors.groupingBy(Model::getStatus, Collectors.counting()),
            // 第二步:把状态计数的Map转换成StockStatusStats
            statusCounts -> {
                StockStatusStats stats = new StockStatusStats();
                // 总数就是所有状态的数量之和
                stats.setTotal(statusCounts.values().stream().mapToLong(Long::longValue).sum());
                // 取出各状态的数量,没有的话默认0
                stats.setInProgress(statusCounts.getOrDefault("Inprogress", 0L));
                stats.setCancel(statusCounts.getOrDefault("Cancel", 0L));
                stats.setCompleted(statusCounts.getOrDefault("Completed", 0L));
                return stats;
            }
        )
    ));

// 输出符合要求的结果
System.out.println("Stock | TOTAL | INPROGRESS | CANCEL | COMPLETED");
System.out.println("-----------------------------------------------");
statsMap.forEach((stockName, stats) -> {
    stats.setStockName(stockName);
    System.out.println(stats);
});

方案2:自定义收集器(更灵活)

如果后续可能要新增状态或者调整统计规则,自定义收集器会更灵活。咱们可以自己实现一个收集器,在流处理过程中直接累加统计数据:

// 自定义收集器,负责累加统计每个股票的状态数据
Collector<Model, StockStatusStats, StockStatusStats> stockStatsCollector = Collector.of(
    // 创建一个空的统计对象
    StockStatusStats::new,
    // 处理每个订单,更新统计数据
    (stats, model) -> {
        stats.setTotal(stats.getTotal() + 1);
        switch (model.getStatus()) {
            case "Inprogress":
                stats.setInProgress(stats.getInProgress() + 1);
                break;
            case "Cancel":
                stats.setCancel(stats.getCancel() + 1);
                break;
            case "Completed":
                stats.setCompleted(stats.getCompleted() + 1);
                break;
            // 以后加新状态,直接在这里加case就行
        }
    },
    // 并行流的时候,合并两个统计对象(不用并行流也建议写上,保证兼容性)
    (stats1, stats2) -> {
        stats1.setTotal(stats1.getTotal() + stats2.getTotal());
        stats1.setInProgress(stats1.getInProgress() + stats2.getInProgress());
        stats1.setCancel(stats1.getCancel() + stats2.getCancel());
        stats1.setCompleted(stats1.getCompleted() + stats2.getCompleted());
        return stats1;
    }
);

// 用自定义收集器分组统计
Map<String, StockStatusStats> statsMap = test.values().stream()
    .collect(Collectors.groupingBy(Model::getName, stockStatsCollector));

// 输出结果
System.out.println("Stock | TOTAL | INPROGRESS | CANCEL | COMPLETED");
System.out.println("-----------------------------------------------");
statsMap.forEach((stockName, stats) -> {
    stats.setStockName(stockName);
    System.out.println(stats);
});

两种方案怎么选?

  • 如果你现在的统计逻辑比较固定,方案1代码更简洁,用现有Collectors组合就能实现,上手快;
  • 如果后续可能要扩展统计规则(比如加新状态、改统计方式),方案2的扩展性更好,只需要修改自定义收集器的累加逻辑就行。

这两种方案都只需要遍历一次数据流,比你原来多次遍历的实现效率更高,代码也更整洁好维护。


内容的提问来源于stack exchange,提问作者vijayk

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 07:52:50