如何使用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 ) ));
代码解释
- flatMap展开:把原Map的每个条目(CityArea对应子Map)拆分成多个小条目,每个小条目包含
cityId、section和对应的ResourceCount,这样方便后续分组。 - 第一次分组聚合:先按
(cityId, section)这个复合键分组,用reducing聚合器把同一组的ResourceCount合并求和。 - 第二次分组整理:把复合键的分组结果转换成
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
相关产品推荐
相关产品推荐

