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

如何使用Java8 Stream和groupBy实现Map多层级聚合结果

Java 8 Stream 实现多层级分组聚合需求

首先先明确咱们用到的实体类定义(你已经提供的):

CityArea类

public class CityArea {
    String cityId;
    String areaPin;

    public String getCityId() { return cityId; }
    public void setCityId(String cityId) { this.cityId = cityId; }
    public String getAreaPin() { return areaPin; }
    public void setAreaPin(String areaPin) { this.areaPin = areaPin; }

    public CityArea(String cityId, String areaPin) {
        super();
        this.cityId = cityId;
        this.areaPin = areaPin;
    }
    public CityArea() { super(); }
}

ResourceCount类

public class ResourceCount {
    Integer streetLightCount;
    Integer waterTankCount;

    public Integer getStreetLightCount() { return streetLightCount; }
    public void setStreetLightCount(Integer streetLightCount) { this.streetLightCount = streetLightCount; }
    public Integer getWaterTankCount() { return waterTankCount; }
    public void setWaterTankCount(Integer waterTankCount) { this.waterTankCount = waterTankCount; }

    public ResourceCount(Integer streetLightCount, Integer waterTankCount) {
        super();
        this.streetLightCount = streetLightCount;
        this.waterTankCount = waterTankCount;
    }
    public ResourceCount() { super(); }
}

需求拆解

咱们的目标是把Map<CityArea, Map<Integer, ResourceCount>>转换成Map<String, Map<Integer, ResourceCount>>,核心是两步聚合:

  • 先按CityArea中的cityId做第一级分组
  • 再按每个子Map中的section(也就是那个Integer键)做第二级分组,同时把同一section下的streetLightCount和waterTankCount分别求和

实现步骤

首先,我们需要一个合并ResourceCount的辅助方法,用来把多个ResourceCount的计数累加:

private static ResourceCount mergeResourceCount(ResourceCount a, ResourceCount b) {
    ResourceCount merged = new ResourceCount();
    merged.setStreetLightCount(a.getStreetLightCount() + b.getStreetLightCount());
    merged.setWaterTankCount(a.getWaterTankCount() + b.getWaterTankCount());
    return merged;
}

接下来,用Java 8 Stream完成多层分组和聚合:

// 原输入Map(替换成你的实际数据)
Map<CityArea, Map<Integer, ResourceCount>> originalMap = new HashMap<>();
// 初始化测试数据(你提供的示例)
CityArea ca1= new CityArea("cityId1", "1");
CityArea ca2= new CityArea("cityId1", "2");
CityArea ca3= new CityArea("cityId2", "1");
CityArea ca4= new CityArea("cityId2", "2");
ResourceCount resourceCount1 = new ResourceCount(10, 10);
ResourceCount resourceCount2 = new ResourceCount(20, 20);
Map<Integer,ResourceCount> resourceMap1 = new HashMap<>();
resourceMap1.put(1, resourceCount1);
resourceMap1.put(2, resourceCount2);
map.put(ca1, resourceMap1);
map.put(ca2, resourceMap1);
Map<Integer,ResourceCount> resourceMap2 = new HashMap<>();
resourceMap2.put(1, resourceCount1);
resourceMap2.put(2, resourceCount2);
map.put(ca3, resourceMap2);
map.put(ca4, resourceMap2);

// 转换为目标Map
Map<String, Map<Integer, ResourceCount>> resultMap = originalMap.entrySet().stream()
    // 第一步:把每个CityArea对应的子Map展开,得到(cityId, section, ResourceCount)的三元组
    .flatMap(entry -> {
        CityArea cityArea = entry.getKey();
        String cityId = cityArea.getCityId();
        return entry.getValue().entrySet().stream()
            .map(subEntry -> new AbstractMap.SimpleEntry<>(
                new AbstractMap.SimpleEntry<>(cityId, subEntry.getKey()),
                subEntry.getValue()
            ));
    })
    // 第二步:按(cityId, section)分组,合并ResourceCount
    .collect(Collectors.groupingBy(
        // 分组键:(cityId, section)
        AbstractMap.SimpleEntry::getKey,
        // 聚合器:合并同组的ResourceCount
        Collectors.reducing(new ResourceCount(0, 0), AbstractMap.SimpleEntry::getValue, YourClass::mergeResourceCount)
    ))
    // 第三步:把分组结果重新整理成目标结构:cityId -> {section: 合并后的ResourceCount}
    .entrySet().stream()
    .collect(Collectors.groupingBy(
        entry -> entry.getKey().getKey(),
        Collectors.toMap(
            entry -> entry.getKey().getValue(),
            Map.Entry::getValue
        )
    ));

代码解释

  1. flatMap展开:把原Map的每个条目(CityArea对应子Map)拆分成多个小条目,每个小条目包含cityId、section和对应的ResourceCount,这样方便后续分组。
  2. 第一次分组聚合:先按(cityId, section)这个复合键分组,用reducing聚合器把同一组的ResourceCount合并求和。
  3. 第二次分组整理:把复合键的分组结果转换成cityId为键,子Map为值的结构,也就是我们需要的最终格式。

测试验证

用你提供的输入数据测试,最终得到的resultMap会和你期望的输出一致:

  • cityId1对应的子Map中,section 1的streetLightCount是20(10+10),waterTankCount是20;section 2的streetLightCount是40(20+20),waterTankCount是40。
  • cityId2的结果和cityId1完全一致,符合预期。

简化优化(可选)

如果觉得用AbstractMap.SimpleEntry太繁琐,也可以用自定义的简单类来存储cityId和section的复合键,代码可读性会更好:

// 自定义复合键类,必须重写equals和hashCode
static class CitySectionKey {
    private String cityId;
    private Integer section;

    public CitySectionKey(String cityId, Integer section) {
        this.cityId = cityId;
        this.section = section;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        CitySectionKey that = (CitySectionKey) o;
        return Objects.equals(cityId, that.cityId) && Objects.equals(section, that.section);
    }

    @Override
    public int hashCode() {
        return Objects.hash(cityId, section);
    }

    // getter方法
    public String getCityId() { return cityId; }
    public Integer getSection() { return section; }
}

然后修改Stream代码:

Map<String, Map<Integer, ResourceCount>> resultMap = originalMap.entrySet().stream()
    .flatMap(entry -> {
        CityArea cityArea = entry.getKey();
        String cityId = cityArea.getCityId();
        return entry.getValue().entrySet().stream()
            .map(subEntry -> new AbstractMap.SimpleEntry<>(
                new CitySectionKey(cityId, subEntry.getKey()),
                subEntry.getValue()
            ));
    })
    .collect(Collectors.groupingBy(
        AbstractMap.SimpleEntry::getKey,
        Collectors.reducing(new ResourceCount(0, 0), AbstractMap.SimpleEntry::getValue, YourClass::mergeResourceCount)
    ))
    .entrySet().stream()
    .collect(Collectors.groupingBy(
        entry -> entry.getKey().getCityId(),
        Collectors.toMap(
            entry -> entry.getKey().getSection(),
            Map.Entry::getValue
        )
    ));

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:42:22