如何基于多属性对Java List分组并聚合关联属性
按多属性分组并聚合Job列表的实现方案
1. 定义数据模型
先明确原始数据的实体类,以及分组后目标的DTO类:
原始实体类(Java 8+)
import java.util.Objects; public class Person { private Integer id; private String name; private Integer age; private String job; // 全参构造器、getter方法 public Person(Integer id, String name, Integer age, String job) { this.id = id; this.name = name; this.age = age; this.job = job; } public Integer getId() { return id; } public String getName() { return name; } public Integer getAge() { return age; } public String getJob() { return job; } // 重写equals和hashCode,仅基于id/name/age三个分组字段 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return Objects.equals(id, person.id) && Objects.equals(name, person.name) && Objects.equals(age, person.age); } @Override public int hashCode() { return Objects.hash(id, name, age); } }
Java 16+ 简化写法(用Record)
如果用Java 16及以上,用Record自动实现equals/hashCode,代码更简洁:
// 原始数据Record public record Person(Integer id, String name, Integer age, String job) {} // 分组后的目标DTO Record public record GroupedPerson(Integer id, String name, Integer age, List<String> jobs) {}
2. 核心分组逻辑
用Stream API的Collectors.groupingBy实现按多属性分组,同时用Collectors.mapping聚合job字段为List:
方式一:直接用原始实体类作为分组键
import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class GroupingExample { public static void main(String[] args) { // 构造原始列表 List<Person> originalList = List.of( new Person(1, "Jane", 25, "job1"), new Person(1, "Jane", 25, "job2"), new Person(1, "Jane", 25, "job3"), new Person(2, "Adam", 30, "job4"), new Person(2, "Adam", 30, "job5") ); // 分组:键为Person(仅匹配id/name/age),值为对应的job列表 Map<Person, List<String>> groupedMap = originalList.stream() .collect(Collectors.groupingBy( // 生成分组键:忽略job字段,仅保留分组属性 p -> new Person(p.getId(), p.getName(), p.getAge(), null), Collectors.mapping(Person::getJob, Collectors.toList()) )); // 转换为目标DTO列表(如果需要) List<GroupedPerson> resultList = groupedMap.entrySet().stream() .map(entry -> new GroupedPerson( entry.getKey().getId(), entry.getKey().getName(), entry.getKey().getAge(), entry.getValue() )) .collect(Collectors.toList()); // 输出验证 resultList.forEach(gp -> System.out.printf("%d %s %d (%s)%n", gp.id(), gp.name(), gp.age(), String.join(", ", gp.jobs()) ) ); } }
方式二:用自定义分组键类(更清晰)
为了避免原始实体类的干扰,可以专门定义一个只包含分组属性的类作为键:
import java.util.Objects; public class GroupKey { private Integer id; private String name; private Integer age; public GroupKey(Integer id, String name, Integer age) { this.id = id; this.name = name; this.age = age; } // 重写equals和hashCode @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) && Objects.equals(age, groupKey.age); } @Override public int hashCode() { return Objects.hash(id, name, age); } // getter方法 public Integer getId() { return id; } public String getName() { return name; } public Integer getAge() { return age; } }
对应的分组代码:
Map<GroupKey, List<String>> groupedMap = originalList.stream() .collect(Collectors.groupingBy( p -> new GroupKey(p.getId(), p.getName(), p.getAge()), Collectors.mapping(Person::getJob, Collectors.toList()) ));
关键注意点
- 分组键的正确性:必须保证相同id/name/age的对象被识别为同一个键,普通类必须重写
equals和hashCode,Record则自动基于所有字段生成(如果用分组专用Record,只包含id/name/age即可)。 - 聚合逻辑:
Collectors.mapping负责提取每个对象的job字段,Collectors.toList()将这些字段收集成List。 - 结果转换:如果不需要Map结构,直接将Map的entry转换为目标DTO列表即可。
内容的提问来源于stack exchange,提问作者Di_
相关产品推荐
相关产品推荐

