
在 Java 开发中,经常需要对对象集合进行分组和聚合操作。当需要根据对象的多个属性进行分组,并对其他属性执行求和等聚合计算时,Java 8 Stream API 提供了强大且灵活的解决方案。本文将详细阐述如何利用 Collectors.groupingBy 结合自定义键和自定义聚合器来实现这一目标。
假设我们有一个 Student 类,包含 name、age、city、salary 和 incentive 等属性:
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; }
// Optional: toString for easy printing
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", city='" + city + '\'' +
", salary=" + salary +
", incentive=" + incentive +
'}';
}
}我们有一个 Student 实例列表,需要根据 name、age 和 city 这三个属性进行分组,然后将每个分组内学生的 salary 和 incentive 进行累加,最终生成一个包含聚合后 Student 对象的列表。
例如,输入数据如下:
立即学习“Java免费学习笔记(深入)”;
Student("Raj",10,"Pune",10000,100)
Student("Raj",10,"Pune",20000,200)
Student("Raj",20,"Pune",10000,100)
Student("Ram",30,"Pune",10000,100)
Student("Ram",30,"Pune",30000,300)
Student("Seema",10,"Pune",10000,100)期望的输出是:
Student("Raj",10,"Pune",30000,300) // (10000+20000), (100+200)
Student("Raj",20,"Pune",10000,100)
Student("Ram",30,"Pune",40000,400) // (10000+30000), (100+300)
Student("Seema",10,"Pune",10000,100)为了实现多属性分组和自定义聚合,我们需要两个核心组件:一个自定义键对象来表示分组依据,以及一个自定义聚合器来处理值的累加。
由于 Map.Entry 只能包含两个元素,无法直接作为多属性分组的键。一个简洁且易于维护的方法是创建一个新的类来封装所有分组属性。对于 Java 8,我们需要手动实现 equals() 和 hashCode() 方法,以确保 Map 能够正确识别相等的键。
import java.util.Objects;
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;
}
// Getters
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);
}
}注意事项:
我们需要一个对象来累积每个分组的 salary 和 incentive。这个聚合器将作为 Collector 的中间容器,在流处理过程中进行状态更新。
import java.util.function.Consumer;
public static class AggregatedValues implements Consumer<Student> {
private String name;
private int age;
private String city;
private double salary;
private double incentive;
// Getters
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 对象并更新聚合状态
@Override
public void accept(Student s) {
// 首次接受时初始化分组键信息
if (name == null) name = s.getName();
if (age == 0) age = s.getAge(); // 假设age不会是0,如果可能,需要更严谨的判断
if (city == null) city = s.getCity();
// 累加薪资和奖金
salary += s.getSalary();
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);
}
// Optional: toString for easy printing
@Override
public String toString() {
return "AggregatedValues{" +
"name='" + name + '\'' +
", age=" + age +
", city='" + city + '\'' +
", salary=" + salary +
", incentive=" + incentive +
'}';
}
}AggregatedValues 的关键点:
现在,我们可以结合 Collectors.groupingBy 和 Collector.of 来执行分组和聚合操作。Collector.of 允许我们构建一个自定义的 Collector,它需要四个函数:
示例代码:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class StudentAggregator {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
// 使用 Collections.addAll 兼容 Java 8,Java 9+ 可用 List.of()
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)
);
// 方案一:聚合结果为 List<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()); // 收集为 List
System.out.println("--- 聚合结果 (AggregatedValues 类型) ---");
aggregatedValuesList.forEach(System.out::println);
System.out.println("\n--- 聚合结果 (Student 类型) ---");
// 方案二:聚合结果直接转换为 List<Student>
List<Student> resultStudents = students.stream()
.collect(Collectors.groupingBy(
NameAgeCity::from, // keyMapper
Collectors.of( // downstream Collector
AggregatedValues::new, // supplier
AggregatedValues::accept, // accumulator
AggregatedValues::merge, // combiner
AggregatedValues::toStudent // finisher: 将 AggregatedValues 转换为 Student
)
))
.values().stream() // 获取 Map 的值(即 Student 列表)
.collect(Collectors.toList()); // 收集为 List
resultStudents.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}通过上述方法,我们成功地利用 Java 8 Stream API 实现了对自定义对象的多属性分组和聚合操作,展示了其在数据处理方面的强大能力和灵活性。这种模式在处理复杂的数据转换和报告生成场景中非常有用。
以上就是Java 8 Stream:按多属性分组聚合自定义对象的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号