Java 8 如何对List<Map>按name分组并聚合其他字段为列表?
按name分组并聚合其他字段为列表的实现方案
你的需求是把同name的集合元素中,除name外的其他字段值分别聚合成列表,而不是把整个Map对象放到列表里。原来的groupingBy只是完成了分组,但没有做字段级的聚合,我们可以通过分组后二次处理或者在分组时使用collectingAndThen进行转换来实现。
方案一:分步实现(直观易懂)
先按name完成分组,再对每个分组内的Map集合进行字段聚合:
import java.util.*; import java.util.stream.Collectors; public class GroupAggregateExample { public static void main(String[] args) { List<Map<String, Object>> allObjs = new ArrayList<>(); Map<String, Object> src = new HashMap<>(); src.put("name", "asset1"); src.put("description", "desc1"); src.put("definition", "def1"); Map<String, Object> src2 = new HashMap<>(); src2.put("name", "asset1"); src2.put("description", "desc2"); src2.put("definition", "def2"); allObjs.add(src); allObjs.add(src2); // 1. 先按name分组,得到{name: [List<Map>]}的结构 Map<String, List<Map<String, Object>>> groupedByName = allObjs.stream() .collect(Collectors.groupingBy(p -> (String) p.get("name"))); // 2. 转换为期望的聚合结构 Map<String, Map<String, Object>> finalResult = new HashMap<>(); groupedByName.forEach((name, mapList) -> { Map<String, Object> aggregatedMap = new HashMap<>(); aggregatedMap.put("name", name); // 收集所有非name的字段,将每个字段的值聚合成列表 // 先获取所有非name的key(考虑到不同Map可能有不同key的情况) Set<String> nonNameKeys = mapList.stream() .flatMap(map -> map.keySet().stream()) .filter(key -> !"name".equals(key)) .collect(Collectors.toSet()); nonNameKeys.forEach(key -> { List<Object> fieldValues = mapList.stream() .map(map -> map.get(key)) .filter(Objects::nonNull) // 过滤掉不存在该key的Map的null值 .collect(Collectors.toList()); aggregatedMap.put(key, fieldValues); }); finalResult.put(name, aggregatedMap); }); // 输出结果,对于你的示例,这里会得到{asset1={name=asset1, description=[desc1, desc2], definition=[def1, def2]}} System.out.println(finalResult); // 如果只需要单个分组的结果(比如只有一个name),可以直接取: // Map<String, Object> singleResult = finalResult.values().iterator().next(); } }
方案二:流式API链式调用(更简洁)
利用Collectors.collectingAndThen在分组完成后直接对结果进行转换,一步到位:
Map<String, Map<String, Object>> finalResult = allObjs.stream() .collect(Collectors.groupingBy( p -> (String) p.get("name"), Collectors.collectingAndThen( Collectors.toList(), mapList -> { Map<String, Object> aggregatedMap = new HashMap<>(); aggregatedMap.put("name", mapList.get(0).get("name")); Set<String> nonNameKeys = mapList.stream() .flatMap(map -> map.keySet().stream()) .filter(key -> !"name".equals(key)) .collect(Collectors.toSet()); nonNameKeys.forEach(key -> { List<Object> fieldValues = mapList.stream() .map(map -> map.get(key)) .filter(Objects::nonNull) .collect(Collectors.toList()); aggregatedMap.put(key, fieldValues); }); return aggregatedMap; } ) ));
关键说明
- 原来的
groupingBy(p -> p.get("name"))只是将同name的整个Map对象收集到列表中,而我们需要的是对每个字段单独聚合。 - 上面的代码考虑了健壮性:如果分组内的Map存在不同的key(比如有的Map没有
description字段),会自动收集所有出现过的非namekey,并过滤掉null值。 - 如果你的业务场景中,所有Map的key都是完全一致的,可以简化代码,直接取第一个Map的key进行处理,不需要收集所有key。
内容的提问来源于stack exchange,提问作者Nishanth
相关产品推荐
相关产品推荐

