如何使用Java8 Stream简化双字段分组后每个key取一个元素的操作
方案解答
完全可以直接在内层groupingBy的下游收集器中完成筛选逻辑,省略二次遍历转换的步骤。
优化后实现代码
你只需要用Collectors.collectingAndThen收集器把「攒同姓名的Person列表」和「调用selectPerson筛选单例」两个步骤合并,再在外层把同年龄下所有姓名的筛选结果收集为列表即可,完整代码如下:
import java.util.List; import java.util.Map; import java.util.stream.Collectors; // 类定义省略 Map<Integer, List<Person>> select(List<Person> persons){ return persons .stream() .collect(Collectors.groupingBy( Person::getAge, // 内层按姓名分组后直接处理为「姓名->筛选后Person」的Map,再转成List Collectors.collectingAndThen( Collectors.groupingBy( Person::getName, // 替换原来的toUnmodifiableList,攒完列表直接调用筛选方法 Collectors.collectingAndThen( Collectors.toUnmodifiableList(), this::selectPerson ) ), name2PersonMap -> name2PersonMap.values() .stream() .toList() // Java 16+ 直接用toList,低版本换为.collect(Collectors.toUnmodifiableList()) ) )); } Person selectPerson(List<Person> persons){ /// 原有自定义筛选逻辑保持不变 }
逻辑说明
- 内层收集器:原来的
toUnmodifiableList()替换为collectingAndThen包装后的收集器,同姓名的Person攒成列表后直接传入selectPerson得到唯一符合要求的实例,内层groupingBy的输出从Map<String, List<Person>>简化为Map<String, Person> - 外层年龄分组的下游收集器:再套一层
collectingAndThen,直接把上面得到的「姓名-筛选后实例」Map的所有value收集为列表,刚好就是你要的每个年龄对应的Person集合 - 整个逻辑只做一次流式处理,无需单独定义
ignoreName转换方法,也不需要二次遍历嵌套Map,和你原来的实现效果完全一致。
示例验证
对应你给出的测试数据,处理逻辑完全符合预期:
- 24岁分组:john、anna各对应1个Person,筛选后得到2个实例组成列表
- 25岁分组:mike对应的2个Person经过selectPerson筛选出1个,加上bob对应的1个Person,组成2个实例的列表
内容的提问来源于stack exchange,提问作者bobo
相关产品推荐
相关产品推荐

