
本教程详细介绍了如何利用java 8 stream api对自定义对象进行多属性分组,并聚合特定字段的值。通过定义复合键对象和自定义累加器,结合`collectors.groupingby`和`collector.of`,可以高效地实现复杂的数据转换,将具有相同分组属性的对象合并为一个聚合对象,从而满足数据统计和处理的需求。
在数据处理中,我们经常需要根据对象的多个属性进行分组,并对分组后的数据执行聚合操作(如求和、计数等)。例如,在一个学生列表中,我们可能需要根据学生的姓名、年龄和城市进行分组,然后计算每个分组中学生的总薪资和总奖金。Java 8引入的Stream API为这类操作提供了强大而灵活的工具。
考虑以下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
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进行求和。
在Collectors.groupingBy操作中,我们需要一个能够唯一标识一组对象的键。当分组条件涉及多个属性时,简单地使用单个属性作为键是不足的。虽然可以使用List.of()(Java 9+)或Arrays.asList()(Java 8)将多个属性打包成一个列表作为键,但这通常会导致代码可读性差且不直观。
立即学习“Java免费学习笔记(深入)”;
更清晰、更具维护性的方法是定义一个专门的类来表示这些复合键。对于Java 8环境,我们可以创建一个静态嵌套类NameAgeCity:
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对象创建
public static NameAgeCity from(Student s) {
return new NameAgeCity(s.getName(), s.getAge(), s.getCity());
}
// 必须重写 equals 和 hashCode 方法,以确保分组的正确性
@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进行求和,我们需要一个累加器来存储中间聚合结果。这个累加器将在Stream处理过程中,接收每个Student对象并更新其内部的聚合值。我们可以创建一个AggregatedValues类来实现这一功能:
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;
// 构造器,通常用于初始化
public AggregatedValues() {
this.salary = 0.0;
this.incentive = 0.0;
}
// 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; }
// `accept` 方法用于累加单个 Student 对象的数据
@Override
public void accept(Student s) {
// 首次遇到某个分组的Student时,初始化分组的标识信息
if (name == null) this.name = s.getName();
if (age == 0) this.age = s.getAge(); // 注意:如果age可能为0,需要更严谨的初始化逻辑
if (city == null) this.city = s.getCity();
this.salary += s.getSalary();
this.incentive += s.getIncentive();
}
// `merge` 方法用于合并两个 AggregatedValues 实例(并行流场景)
public AggregatedValues merge(AggregatedValues other) {
this.salary += other.salary;
this.incentive += other.incentive;
return this;
}
// Optional: toString for easy printing
@Override
public String toString() {
return "AggregatedValues{" + "name='" + name + '\'' + ", age=" + age + ", city='" + city + '\'' + ", salary=" + salary + ", incentive=" + incentive + '}';
}
}注意事项:
现在,我们有了复合键NameAgeCity和累加器AggregatedValues,可以结合Java 8 Stream API的Collectors.groupingBy和Collector.of来实现最终的逻辑。
Collectors.groupingBy允许我们指定一个键映射函数和一个下游收集器(downstream collector)。在这里,键映射函数将Student对象转换为NameAgeCity,而下游收集器则负责将属于同一组的Student对象聚合成一个AggregatedValues实例。
Collector.of方法允许我们自定义一个收集器,它需要四个参数:
完整的实现代码如下:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.function.Consumer;
import java.util.Objects; // For NameAgeCity
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 String getName() { return name; }
public int getAge() { return age; }
public String getCity() { return 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 implements Consumer<Student> {
private String name;
private int age;
private String city;
private double salary;
private double incentive;
public AggregatedValues() {
this.salary = 0.0;
this.incentive = 0.0;
}
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 void accept(Student s) {
if (name == null) this.name = s.getName();
if (age == 0) this.age = s.getAge(); // 简化处理,实际可能需更严谨
if (city == null) this.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;
}
// 添加一个方法,将AggregatedValues转换回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 + '}';
}
}
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
Collections.addAll(students, // Java 8 兼容方式添加元素
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)
);
// 使用 Collectors.groupingBy 和 Collector.of 进行分组和聚合
List<Student> resultStudents = students.stream()
.collect(Collectors.groupingBy(
NameAgeCity::from, // keyMapper: 将Student映射为复合键
Collectors.of( // downstream collector: 自定义聚合逻辑
AggregatedValues::new, // supplier: 创建新的AggregatedValues实例
AggregatedValues::accept, // accumulator: 将Student数据累加到AggregatedValues
AggregatedValues::merge, // combiner: 合并两个AggregatedValues实例
AggregatedValues::toStudent // finisher: 将最终的AggregatedValues转换回Student
)
))
.values() // 获取Map中的所有聚合值 (AggregatedValues实例)
.stream() // 将Collection<Student>转换为Stream<Student>
.collect(Collectors.toList()); // 收集为List<Student>
resultStudents.forEach(System.out::println);
}
}输出结果:
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}在上述示例中,我们希望最终结果仍然是Student对象的列表,而不是AggregatedValues对象的列表。Collector.of的第四个参数finisher函数正为此目的而设计。它会在所有元素都被累加和合并之后,对最终的结果容器进行一次转换。
我们在AggregatedValues类中添加了一个toStudent()方法,用于将聚合后的数据封装回Student对象。
public static class AggregatedValues implements Consumer<Student> {
// ... 其他代码 ...
public Student toStudent() {
return new Student(name, age, city, salary, incentive);
}
}然后,在Collector.of中,我们将AggregatedValues::toStudent作为finisher函数传入。这样,groupingBy操作的最终结果Map的value类型将直接是Student,省去了后续的map操作。
通过本教程,我们学习了如何利用Java 8 Stream API对自定义对象进行多属性分组和聚合。关键点包括:
这种方法不仅适用于数值求和,还可以扩展到其他聚合操作,如计算平均值、查找最大/最小值等,只需相应地调整AggregatedValues类中的accept和merge逻辑。它提供了一种结构清晰、易于维护且高效处理复杂数据转换的解决方案。
以上就是使用Java 8 Stream API对自定义对象进行多属性分组与聚合操作指南的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号