如何用Java Streams按Key分组对象并聚合唯一子对象集合
Java Streams 实现分组聚合方案
实体类参考(需确保B/C实现equals和hashCode)
// 源对象A0 class A0 { private Long id; private String name; private B b; private C c; // 构造器、Getter方法省略 } // 目标对象A class A { private Long id; private String name; private Set<B> uniqueBs; private Set<C> uniqueCs; // 全参构造器 public A(Long id, String name, Set<B> uniqueBs, Set<C> uniqueCs) { this.id = id; this.name = name; this.uniqueBs = uniqueBs; this.uniqueCs = uniqueCs; } // Getter方法省略 } // B、C类需正确实现equals和hashCode以保证集合去重 class B { // 字段、equals、hashCode实现 } class C { // 字段、equals、hashCode实现 }
核心Streams转换实现
import java.util.*; import java.util.stream.Collectors; public class Converter { public List<A> convertA0ListToAList(List<A0> a0List) { return a0List.stream() // 按id+name分组,用自定义GroupKey作为分组键(比JDK内部Entry更规范) .collect(Collectors.groupingBy( a0 -> new GroupKey(a0.getId(), a0.getName()), // 对每个分组的A0列表聚合去重后的B、C集合,转换为A对象 Collectors.collectingAndThen( Collectors.toList(), group -> { // 提取并去重B对象,用LinkedHashSet保留顺序(可选换HashSet) Set<B> uniqueBs = group.stream() .map(A0::getB) .filter(Objects::nonNull) // 可选:过滤null值 .collect(Collectors.toCollection(LinkedHashSet::new)); // 提取并去重C对象 Set<C> uniqueCs = group.stream() .map(A0::getC) .filter(Objects::nonNull) .collect(Collectors.toCollection(LinkedHashSet::new)); // 从分组中取id和name(分组内所有A0的id/name一致) A0 first = group.get(0); return new A(first.getId(), first.getName(), uniqueBs, uniqueCs); } ) )) .values() // 提取所有分组后的A对象 .stream() .collect(Collectors.toList()); } // 自定义不可变分组键类,确保分组逻辑正确 private static class GroupKey { private final Long id; private final String name; public GroupKey(Long id, String name) { this.id = id; this.name = name; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; GroupKey groupKey = (GroupKey) o; return Objects.equals(id, groupKey.id) && Objects.equals(name, groupKey.name); } @Override public int hashCode() { return Objects.hash(id, name); } } }
关键要点说明
- 分组键设计:用自定义
GroupKey替代JDK的AbstractMap.SimpleEntry,避免依赖内部类,同时通过重写equals和hashCode保证分组逻辑准确。 - 去重逻辑:必须确保B和C类正确实现
equals和hashCode,否则Set无法完成去重。如果需要保留元素顺序,用LinkedHashSet;无需顺序则用HashSet即可。 - 空值处理:添加
filter(Objects::nonNull)可以避免将null值加入集合,根据业务需求可选择保留或移除该逻辑。
内容的提问来源于stack exchange,提问作者Ivan B.
相关产品推荐
相关产品推荐

