你真的懂Reduce吗?
想象一下,你有一盒散落的乐高积木(没错,就是那些让你头疼的小块),你的任务是把这些积木拼成一个完整的城堡。笨办法是一块一块地拿起来比对、拼接,累得半死还容易拼错。而Reduce就像是有一个智能助手,它帮你把这些积木一块一块地”折叠”起来,最后直接给你一个完整的城堡模型。
在Java的世界里,集合数据就是一盒散落的乐高积木,而reduce操作就是那个智能助手,帮你高效地处理数据。
先从最基础的说起
什么是reduce?
简单来说,reduce是一种归约操作,它把一个流(Stream)中的所有元素,通过一个累积函数,最终合并成一个结果。这个结果可以是一个值,也可以是一个新的集合。
看代码吧,这样更直观:
import java.util.Arrays;
import java.util.List;
public class ReduceBasic {
public static void main(String[] args) {
// 模拟一组学生成绩
List<Integer> scores = Arrays.asList(85, 92, 78, 95, 88);
// 使用reduce计算总分
Integer totalScore = scores.stream()
.reduce(0, (a, b) -> a + b);
System.out.println("总分: " + totalScore); // 输出: 总分: 438
// 使用reduce计算平均分
double average = scores.stream()
.mapToInt(Integer::intValue)
.average()
.orElse(0.0);
System.out.println("平均分: " + average); // 输出: 平均分: 87.6
}
}
看到没?(a, b) -> a + b这个lambda表达式就是累积函数,它告诉Java如何把两个数加在一起。
reduce的三种常见形式
1. 不带初始值的reduce
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 计算乘积
Optional<Integer> product = numbers.stream()
.reduce((a, b) -> a * b);
// 注意:结果可能在Optional中,因为如果流为空,就没有初始值
if (product.isPresent()) {
System.out.println("乘积: " + product.get()); // 输出: 乘积: 120
}
2. 带初始值的reduce
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 计算总和,初始值为0
Integer sum = numbers.stream()
.reduce(0, (a, b) -> a + b);
System.out.println("总和: " + sum); // 输出: 总和: 15
// 计算最大数,初始值为0
Integer max = numbers.stream()
.reduce(0, (a, b) -> Math.max(a, b));
System.out.println("最大值: " + max); // 输出: 最大值: 5
3. 带初始值、累积函数和组合函数的reduce
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 计算总和
Integer sum = numbers.stream()
.reduce(0,
(a, b) -> a + b, // 累积函数
(a, b) -> a + b); // 组合函数
System.out.println("总和: " + sum); // 输出: 总和: 15
这个形式看起来复杂,但实际上它支持并行流。当数据量很大时,Java会把流分成多份,分别在多个线程中处理,然后用组合函数把结果合并起来。
实战:处理学生成绩数据
假设你是一名老师,你需要处理一班学生的成绩数据。
第一步:定义学生类
import java.util.List;
public class Student {
private String name;
private int chinese;
private int math;
private int english;
// 构造方法
public Student(String name, int chinese, int math, int english) {
this.name = name;
this.chinese = chinese;
this.math = math;
this.english = english;
}
// Getter方法
public String getName() { return name; }
public int getChinese() { return chinese; }
public int getMath() { return math; }
public int getEnglish() { return english; }
public int getTotal() { return chinese + math + english; }
}
第二步:创建学生列表
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class StudentGradeProcessor {
public static void main(String[] args) {
// 创建学生列表
List<Student> students = Arrays.asList(
new Student("张三", 85, 92, 78),
new Student("李四", 90, 88, 95),
new Student("王五", 78, 85, 82),
new Student("赵六", 92, 90, 88),
new Student("钱七", 88, 85, 90)
);
System.out.println("=== 学生成绩处理 ===");
processStudentGrades(students);
}
private static void processStudentGrades(List<Student> students) {
// 1. 计算总分最高的学生
System.out.println("\n【总分最高学生】");
Optional<Student> topStudent = students.stream()
.reduce((a, b) -> a.getTotal() > b.getTotal() ? a : b);
topStudent.ifPresent(s ->
System.out.println("姓名: " + s.getName() +
", 总分: " + s.getTotal() +
" (语文:" + s.getChinese() +
", 数学:" + s.getMath() +
", 英语:" + s.getEnglish() + ")"));
// 2. 计算各科平均分
System.out.println("\n【各科平均分】");
double avgChinese = students.stream()
.mapToInt(Student::getChinese)
.average()
.orElse(0.0);
double avgMath = students.stream()
.mapToInt(Student::getMath)
.average()
.orElse(0.0);
double avgEnglish = students.stream()
.mapToInt(Student::getEnglish)
.average()
.orElse(0.0);
System.out.printf("语文平均分: %.2f%n", avgChinese);
System.out.printf("数学平均分: %.2f%n", avgMath);
System.out.printf("英语平均分: %.2f%n", avgEnglish);
// 3. 计算总平均分
System.out.println("\n【总平均分】");
double totalAverage = students.stream()
.mapToInt(Student::getTotal)
.average()
.orElse(0.0);
System.out.printf("总平均分: %.2f%n", totalAverage);
// 4. 统计各科及格人数
System.out.println("\n【各科及格人数】");
long passChinese = students.stream()
.filter(s -> s.getChinese() >= 60)
.count();
long passMath = students.stream()
.filter(s -> s.getMath() >= 60)
.count();
long passEnglish = students.stream()
.filter(s -> s.getEnglish() >= 60)
.count();
System.out.println("语文及格人数: " + passChinese);
System.out.println("数学及格人数: " + passMath);
System.out.println("英语及格人数: " + passEnglish);
// 5. 找出三科都及格的学生
System.out.println("\n【三科都及格的学生】");
students.stream()
.filter(s -> s.getChinese() >= 60 &&
s.getMath() >= 60 &&
s.getEnglish() >= 60)
.forEach(s -> System.out.println("姓名: " + s.getName()));
// 6. 使用reduce计算总分
System.out.println("\n【使用reduce计算总分】");
Integer totalSum = students.stream()
.mapToInt(Student::getTotal)
.reduce(0, (a, b) -> a + b);
System.out.println("班级总分: " + totalSum);
// 7. 找出单科最高分
System.out.println("\n【单科最高分】");
Integer maxChinese = students.stream()
.mapToInt(Student::getChinese)
.reduce(0, (a, b) -> Math.max(a, b));
Integer maxMath = students.stream()
.mapToInt(Student::getMath)
.reduce(0, (a, b) -> Math.max(a, b));
Integer maxEnglish = students.stream()
.mapToInt(Student::getEnglish)
.reduce(0, (a, b) -> Math.max(a, b));
System.out.println("语文最高分: " + maxChinese);
System.out.println("数学最高分: " + maxMath);
System.out.println("英语最高分: " + maxEnglish);
}
}
运行这个程序,你会看到这样的输出:
=== 学生成绩处理 ===
【总分最高学生】
姓名: 李四, 总分: 273 (语文:90, 数学:88, 英语:95)
【各科平均分】
语文平均分: 86.60
数学平均分: 88.00
英语平均分: 86.60
【总平均分】
总平均分: 261.00
【各科及格人数】
语文及格人数: 5
数学及格人数: 5
英语及格人数: 5
【三科都及格的学生】
姓名: 张三
姓名: 李四
姓名: 王五
姓名: 赵六
姓名: 钱七
【使用reduce计算总分】
班级总分: 1305
【单科最高分】
语文最高分: 92
数学最高分: 92
英语最高分: 95
更复杂的实战:处理订单数据
现在,假设你是一名电商公司的数据分析师,需要处理订单数据。
第一步:定义订单类
import java.time.LocalDate;
public class Order {
private String orderId;
private String customerName;
private double amount;
private String category;
private LocalDate orderDate;
// 构造方法
public Order(String orderId, String customerName, double amount,
String category, LocalDate orderDate) {
this.orderId = orderId;
this.customerName = customerName;
this.amount = amount;
this.category = category;
this.orderDate = orderDate;
}
// Getter方法
public String getOrderId() { return orderId; }
public String getCustomerName() { return customerName; }
public double getAmount() { return amount; }
public String getCategory() { return category; }
public LocalDate getOrderDate() { return orderDate; }
}
第二步:处理订单数据
”`java import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import java.time.LocalDate;
public class OrderProcessor {
public static void main(String[] args) {
// 创建订单列表
List<Order> orders = Arrays.asList(
new Order("ORD001", "张三", 299.99, "电子产品", LocalDate.of(2023, 10, 1)),
new Order("ORD002", "李四", 149.50, "服装", LocalDate.of(2023, 10, 1)),
new Order("ORD003", "王五", 599.99, "电子产品", LocalDate.of(2023, 10, 2)),
new Order("ORD004", "赵六", 89.99, "图书", LocalDate.of(2023, 10, 2)),
new Order("ORD005", "钱七", 449.99, "电子产品", LocalDate.of(2023, 10, 3)),
new Order("ORD006", "孙八", 199.99, "服装", LocalDate.of(2023, 10, 3)),
new Order("ORD007", "周九", 79.99, "图书", LocalDate.of(2023, 10, 4)),
new Order("ORD008", "吴十", 999.99, "电子产品", LocalDate.of(2023, 10, 4))
);
System.out.println("=== 订单数据处理 ===");
processOrders(orders);
}
private static void processOrders(List<Order> orders) {
// 1. 计算总销售额
System.out.println("\n【总销售额】");
double totalSales = orders.stream()
.mapToDouble(Order::getAmount)
.reduce(0.0, (a, b) -> a + b);
System.out.printf("总销售额: ¥%.2f%n", totalSales);
// 2. 计算平均订单金额
System.out.println("\n【平均订单金额】");
double averageOrder = orders.stream()
.mapToDouble(Order::getAmount)
.average()
.orElse(0.0);
System.out.printf("平均订单金额: ¥%.2f%n", averageOrder);
// 3. 找出最大订单金额
System.out.println("\n【最大订单金额】");
double maxOrder = orders.stream()
.mapToDouble(Order::getAmount)
.reduce(0.0, (a, b) -> Math.max(a, b));
System.out.printf("最大订单金额: ¥%.2f%n", maxOrder);
// 4. 找出最小订单金额
System.out.println("\n【最小订单金额】");
double minOrder = orders.stream()
.mapToDouble(Order::getAmount)
.reduce(Double.MAX_VALUE, (a, b) -> Math.min(a, b));
System.out.printf("最小订单金额: ¥%.2f%n", minOrder);
// 5. 按类别统计销售额
System.out.println("\n【按类别统计销售额】");
Map<String, Double> salesByCategory = orders.stream()
.collect(Collectors.groupingBy(
Order::getCategory,
Collectors.summingDouble(Order::getAmount)
));
salesByCategory.forEach((category, amount) ->
System.out.printf("%s: ¥%.2f%n", category, amount));
// 6. 找出每个类别的最大订单
System.out.println("\n【每个类别的最大订单】");
orders.stream()
.collect(Collectors.groupingBy(Order::getCategory))
.forEach((category, categoryOrders) -> {
Order maxOrderInCategory = categoryOrders.stream()
.reduce((a, b) -> a.getAmount() > b.getAmount() ? a : b)
.orElse(null);
if (maxOrderInCategory != null) {
System.out.printf("%s: %s (¥%.2f)%n",
category,
maxOrderInCategory.getOrderId(),
maxOrderInCategory.getAmount());
}
});
// 7. 统计每天的订单数量和销售额
System.out.println("\n【每天的订单统计】");
orders.stream()
.collect(Collectors.groupingBy(Order::getOrderDate))
.forEach((date, dayOrders) -> {
long orderCount = dayOrders.size();
double daySales = dayOrders.stream()
.mapToDouble(Order::getAmount)
.reduce(0.0, (a, b) -> a + b);
System.out.printf("%s: %d单, 销售额 ¥%.2f%n",
date, orderCount, daySales);
});
// 8. 找出消费最多的客户
System.out.println("\n【消费最多的客户】");
Map<String, Double> customerSpending = orders.stream()
.collect(Collectors.groupingBy(
Order::getCustomerName,
Collectors.summingDouble(Order::getAmount)
));
customerSpending.entrySet().stream()
.max(Map.Entry.comparingByValue())
.ifPresent(entry ->
System.out.printf("客户: %s, 总消费: ¥%.2f%n",
entry.getKey(), entry.getValue()));
// 9. 计算订单金额的总和(使用reduce)
System.out.println("\n【订单金额总和(使用reduce)】");
double sumUsingReduce = orders.stream()
.mapToDouble(Order::getAmount)
.reduce(0.0, (a, b) -> a + b);
System.out.printf("订单金额总和: ¥%.2f%n", sumUsingReduce);
// 10. 找出订单金额超过平均值的订单
System.out.println("\n【订单金额超过平均值的订单】");
orders.stream()
.filter(order -> order.getAmount() > averageOrder)
.forEach(order ->
System.out.printf("订单ID: %s, 金额: ¥%.2f, 客户: %s%n",
order.getOrderId(),
order.getAmount(),
order.getCustomerName()));
}