
本教程详细介绍了如何利用 java 8 stream api,对自定义对象列表进行多属性分组,并对指定数值字段进行聚合求和。通过引入自定义复合键类和聚合容器,结合 `collectors.groupingby` 和 `collector.of`,实现了高效、灵活的数据处理,将具有相同名称、年龄和城市的学生数据合并,并累加其薪资和奖金,最终生成聚合后的新列表。
在数据处理中,我们经常需要对列表中的对象进行分组,并根据分组结果对某些属性进行聚合计算。例如,在一个学生列表中,我们可能需要根据学生的姓名、年龄和城市进行分组,然后统计每个分组的总薪资和总奖金。Java 8 引入的 Stream API 提供了强大的功能来处理这类问题,但对于涉及多属性分组和自定义聚合逻辑的场景,需要巧妙地结合 Collectors 来实现。
假设我们有一个 Student 类,包含姓名、年龄、城市、薪资和奖金等属性:
public class Student {
private String name;
private int age;
private String city;
private double salary;
private double incentive;
public Student(String name, int age, String city, double salary, double incentive) {
this.name = name;
this.age = age;
this.city = city;
this.salary = salary;
this.incentive = incentive;
}
// Getters for all fields
public String getName() { return name; }
public int getAge() { return age; }
public String getCity() { return city; }
public double getSalary() { return salary; }
public double getIncentive() { return incentive; }
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", city='" + city + '\'' +
", salary=" + salary +
", incentive=" + incentive +
'}';
}
}我们的目标是将具有相同 name、age 和 city 的学生进行分组,并将其 salary 和 incentive 进行累加。
初次尝试时,开发者可能倾向于使用 Collectors.toMap,并尝试将多个属性作为 Map 的键。例如,使用 AbstractMap.SimpleEntry:
立即学习“Java免费学习笔记(深入)”;
// 编译错误示例 // List<Student> res = new ArrayList<>(students.stream() // .collect(Collectors.toMap( // ec -> new AbstractMap.SimpleEntry<>(ec.getName(), ec.getAge(), ec.getCity()), // 编译错误:SimpleEntry只接受两个参数 // Function.identity(), // (a, b) -> new Student( // a.getName(), a.getAge(), a.getCity(), a.getSalary() + b.getSalary(), a.getIncentive() + b.getIncentive() // ) // )) // .values());
这个尝试会遇到两个主要问题:
为了解决多属性分组的问题,我们需要一个能够封装这些属性并正确实现 equals 和 hashCode 方法的自定义对象作为 Map 的键。
要将多个属性作为一个整体进行分组,最清晰且可维护的方式是创建一个专门的类来表示这个复合键。对于 Java 8,我们需要手动实现 equals 和 hashCode 方法。
public static class NameAgeCity {
private String name;
private int age;
private String city;
public NameAgeCity(String name, int age, String city) {
this.name = name;
this.age = age;
this.city = city;
}
public String getName() { return name; }
public int getAge() { return age; }
public String getCity() { return city; }
// 静态工厂方法,方便从 Student 对象创建 NameAgeCity 实例
public static NameAgeCity from(Student s) {
return new NameAgeCity(s.getName(), s.getAge(), s.getCity());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NameAgeCity that = (NameAgeCity) o;
return age == that.age &&
Objects.equals(name, that.name) &&
Objects.equals(city, that.city);
}
@Override
public int hashCode() {
return Objects.hash(name, age, city);
}
@Override
public String toString() {
return "NameAgeCity{" +
"name='" + name + '\'' +
", age=" + age +
", city='" + city + '\'' +
'}';
}
}重要提示:
在分组之后,我们需要将每个分组内的 salary 和 incentive 进行累加。由于我们希望得到一个聚合后的新对象,而不是修改原始 Student 对象,或者如果 Student 对象是不可变的,我们可以引入一个专门的类 AggregatedValues 来存储聚合结果。
AggregatedValues 将充当一个可变的累加器,它会在流处理过程中收集和合并数据。
public static class AggregatedValues {
private String name;
private int age;
private String city;
private double salary;
private double incentive;
// 默认构造函数,用于 Collectors.of 的 supplier
public AggregatedValues() {
// 初始值通常为0或null
}
// Getters for aggregated values
public String getName() { return name; }
public int getAge() { return age; }
public String getCity() { return city; }
public double getSalary() { return salary; }
public double getIncentive() { return incentive; }
// 累加器方法:将一个 Student 对象的数据累加到当前 AggregatedValues 实例
public void accept(Student s) {
// 首次接受 Student 时,初始化基本信息
if (name == null) name = s.getName();
if (age == 0) age = s.getAge(); // 假设age不会是0作为有效分组键
if (city == null) city = s.getCity();
// 累加薪资和奖金
this.salary += s.getSalary();
this.incentive += s.getIncentive();
}
// 合并器方法:将另一个 AggregatedValues 实例的数据合并到当前实例
public AggregatedValues merge(AggregatedValues other) {
this.salary += other.salary;
this.incentive += other.incentive;
return this; // 返回当前实例以支持链式调用
}
// 可选:将聚合结果转换回 Student 对象
public Student toStudent() {
return new Student(name, age, city, salary, incentive);
}
@Override
public String toString() {
return "AggregatedValues{" +
"name='" + name + '\'' +
", age=" + age +
", city='" + city + '\'' +
", salary=" + salary +
", incentive=" + incentive +
'}';
}
}现在,我们可以将上述两个方案结合起来,使用 Collectors.groupingBy 进行分组,并使用 Collector.of 创建一个自定义的下游收集器来执行聚合操作。
Collector.of 方法需要四个参数:
完整示例代码:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class StudentAggregator {
// Student 类定义 (如上所示)
public static class Student {
private String name;
private int age;
private String city;
private double salary;
private double incentive;
public Student(String name, int age, String city, double salary, double incentive) {
this.name = name;
this.age = age;
this.city = city;
this.salary = salary;
this.incentive = incentive;
}
public String getName() { return name; }
public int getAge() { return age; }
public String getCity() { return city; }
public double getSalary() { return salary; }
public double getIncentive() { return incentive; }
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", city='" + city + '\'' +
", salary=" + salary +
", incentive=" + incentive +
'}';
}
}
// NameAgeCity 复合键类定义 (如上所示)
public static class NameAgeCity {
private String name;
private int age;
private String city;
public NameAgeCity(String name, int age, String city) {
this.name = name;
this.age = age;
this.city = city;
}
public static NameAgeCity from(Student s) {
return new NameAgeCity(s.getName(), s.getAge(), s.getCity());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NameAgeCity that = (NameAgeCity) o;
return age == that.age &&
Objects.equals(name, that.name) &&
Objects.equals(city, that.city);
}
@Override
public int hashCode() {
return Objects.hash(name, age, city);
}
@Override
public String toString() {
return "NameAgeCity{" +
"name='" + name + '\'' +
", age=" + age +
", city='" + city + '\'' +
'}';
}
}
// AggregatedValues 聚合容器类定义 (如上所示)
public static class AggregatedValues {
private String name;
private int age;
private String city;
private double salary;
private double incentive;
public AggregatedValues() { }
public String getName() { return name; }
public int getAge() { return age; }
public String getCity() { return city; }
public double getSalary() { return salary; }
public double getIncentive() { return incentive; }
public void accept(Student s) {
if (name == null) name = s.getName();
if (age == 0) age = s.getAge(); // Assuming age 0 is not a valid grouping key initially
if (city == null) city = s.getCity();
this.salary += s.getSalary();
this.incentive += s.getIncentive();
}
public AggregatedValues merge(AggregatedValues other) {
this.salary += other.salary;
this.incentive += other.incentive;
return this;
}
public Student toStudent() {
return new Student(name, age, city, salary, incentive);
}
@Override
public String toString() {
return "AggregatedValues{" +
"name='" + name + '\'' +
", age=" + age +
", city='" + city + '\'' +
", salary=" + salary +
", incentive=" + incentive +
'}';
}
}
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
// For Java 8, use Collections.addAll or Arrays.asList for list initialization
Collections.addAll(students,
new Student("Raj", 10, "Pune", 10000, 100),
new Student("Raj", 10, "Pune", 20000, 200),
new Student("Raj", 20, "Pune", 10000, 100),
new Student("Ram", 30, "Pune", 10000, 100),
new Student("Ram", 30, "Pune", 30000, 300),
new Student("Seema", 10, "Pune", 10000, 100)
);
// 方案一:聚合结果为 AggregatedValues 列表
List<AggregatedValues> aggregatedValuesList = students.stream()
.collect(Collectors.groupingBy(
NameAgeCity::from, // keyMapper: 将 Student 映射为 NameAgeCity 复合键
Collectors.of( // downstream Collector: 自定义聚合逻辑
AggregatedValues::new, // supplier: 创建新的 AggregatedValues 实例
AggregatedValues::accept, // accumulator: 将 Student 累加到 AggregatedValues
AggregatedValues::merge // combiner: 合并两个 AggregatedValues 实例
)
))
.values().stream() // 获取 Map 的所有值 (AggregatedValues 实例)
.collect(Collectors.toList()); // 收集为列表
System.out.println("--- AggregatedValues 列表 ---");
aggregatedValuesList.forEach(System.out::println);
// 方案二:聚合结果直接转换为 Student 列表 (使用 finisher)
List<Student> aggregatedStudentsList = students.stream()
.collect(Collectors.groupingBy(
NameAgeCity::from, // keyMapper
Collectors.of( // downstream Collector
AggregatedValues::new, // supplier
AggregatedValues::accept, // accumulator
AggregatedValues::merge, // combiner
AggregatedValues::toStudent // finisherFunction: 将 AggregatedValues 转换为 Student
)
))
.values().stream() // 获取 Map 的所有值 (此时已经是 Student 实例)
.collect(Collectors.toList()); // 收集为列表
System.out.println("\n--- 聚合后的 Student 列表 ---");
aggregatedStudentsList.forEach(System.out::println);
}
}输出结果:
--- AggregatedValues 列表 ---
AggregatedValues{name='Raj', age=20, city='Pune', salary=10000.0, incentive=100.0}
AggregatedValues{name='Raj', age=10, city='Pune', salary=30000.0, incentive=300.0}
AggregatedValues{name='Ram', age=30, city='Pune', salary=40000.0, incentive=400.0}
AggregatedValues{name='Seema', age=10, city='Pune', salary=10000.0, incentive=100.0}
--- 聚合后的 Student 列表 ---
Student{name='Raj', age=20, city='Pune', salary=10000.0, incentive=100.0}
Student{name='Raj', age=10, city='Pune', salary=30000.0, incentive=300.0}
Student{name='Ram', age=30, city='Pune', salary=40000.0, incentive=400.0}
Student{name='Seema', age=10, city='Pune', salary=10000.0, incentive=100.0}可以看到,Raj, 10, Pune 的学生数据被正确聚合,薪资和奖金分别累加为 30000 和 300。
以上就是Java 8 Stream 多属性分组与聚合:自定义对象列表处理教程的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号