Java Stream统计案例怎么聚合数据

wen java案例 24

本文目录导读:

Java Stream统计案例怎么聚合数据

  1. 案例背景:员工数据统计
  2. 基本统计(计数、求和、平均、最大、最小)
  3. 分组聚合(按部门统计)
  4. 自定义聚合(使用 reduce)
  5. 分组合并(将分组后的数据收集到 List)
  6. 综合案例:生成一份完整的统计报告
  7. 聚合方法速查表

Java Stream 提供了丰富的 聚合(Aggregation) 操作,主要有三大类统计方式:预定义聚合函数自定义归约分组聚合

下面通过一个具体的案例来演示如何用 Stream 聚合数据。

案例背景:员工数据统计

假设我们有一个 Employee 类,包含姓名、部门、薪资和年龄。

import java.util.*;
import java.util.stream.Collectors;
public class Employee {
    private String name;
    private String dept;
    private double salary;
    private int age;
    // 构造器、getter/setter 略... 
    // 为了方便演示,手动生成一些数据
    public static List<Employee> getData() {
        return Arrays.asList(
            new Employee("张三", "技术部", 15000, 28),
            new Employee("李四", "技术部", 18000, 32),
            new Employee("王五", "市场部", 12000, 35),
            new Employee("赵六", "市场部", 14000, 29),
            new Employee("钱七", "技术部", 22000, 40),
            new Employee("孙八", "人事部", 10000, 25)
        );
    }
}

基本统计(计数、求和、平均、最大、最小)

使用 Collectors.summarizingDouble()

这是最全面的方法,返回一个包含所有统计值的对象。

public static void basicStatistics() {
    List<Employee> employees = Employee.getData();
    // 一次性获取薪资的所有统计指标
    DoubleSummaryStatistics salaryStats = employees.stream()
        .collect(Collectors.summarizingDouble(Employee::getSalary));
    System.out.println("薪资统计:");
    System.out.println("总数: " + salaryStats.getCount());
    System.out.println("总和: " + salaryStats.getSum());
    System.out.println("平均: " + salaryStats.getAverage());
    System.out.println("最高: " + salaryStats.getMax());
    System.out.println("最低: " + salaryStats.getMin());
}

分别使用专用方法

public static void separateStatistics() {
    List<Employee> employees = Employee.getData();
    long count = employees.stream().count();  // 或 .collect(Collectors.counting())
    double sum = employees.stream().mapToDouble(Employee::getSalary).sum();
    OptionalDouble avg = employees.stream().mapToDouble(Employee::getSalary).average();
    OptionalDouble max = employees.stream().mapToDouble(Employee::getSalary).max();
    OptionalDouble min = employees.stream().mapToDouble(Employee::getSalary).min();
    System.out.println("总数: " + count);
    System.out.println("总和: " + sum);
    System.out.println("平均: " + avg.orElse(0));
    System.out.println("最高: " + max.orElse(0));
    System.out.println("最低: " + min.orElse(0));
}

分组聚合(按部门统计)

这是 Stream 最强大的功能之一,可以按某个字段分组后,对每组进行统计。

1 统计每个部门的员工数

public static void groupByDeptCount() {
    List<Employee> employees = Employee.getData();
    Map<String, Long> deptCount = employees.stream()
        .collect(Collectors.groupingBy(
            Employee::getDept,
            Collectors.counting()  // 计数
        ));
    System.out.println("各部门人数: " + deptCount);
    // 输出: {人事部=1, 技术部=3, 市场部=2}
}

2 统计每个部门的薪资总和

public static void groupByDeptSumSalary() {
    List<Employee> employees = Employee.getData();
    Map<String, Double> deptSalarySum = employees.stream()
        .collect(Collectors.groupingBy(
            Employee::getDept,
            Collectors.summingDouble(Employee::getSalary)
        ));
    System.out.println("各部门薪资总和: " + deptSalarySum);
    // 输出: {人事部=10000.0, 技术部=55000.0, 市场部=26000.0}
}

3 统计每个部门的薪资平均值

public static void groupByDeptAvgSalary() {
    List<Employee> employees = Employee.getData();
    Map<String, Double> deptAvgSalary = employees.stream()
        .collect(Collectors.groupingBy(
            Employee::getDept,
            Collectors.averagingDouble(Employee::getSalary)
        ));
    System.out.println("各部门平均薪资: " + deptAvgSalary);
    // 输出: {人事部=10000.0, 技术部=18333.333..., 市场部=13000.0}
}

4 一次统计多个指标(最实用)

使用 collectingAndThen 或嵌套的 summarizingDouble

public static void groupByDeptMultipleStats() {
    List<Employee> employees = Employee.getData();
    Map<String, DoubleSummaryStatistics> deptStats = employees.stream()
        .collect(Collectors.groupingBy(
            Employee::getDept,
            Collectors.summarizingDouble(Employee::getSalary)
        ));
    // 遍历输出
    deptStats.forEach((dept, stats) -> {
        System.out.printf("部门: %s, 人数: %d, 总薪资: %.0f, 平均: %.2f, 最高: %.0f, 最低: %.0f%n",
            dept, stats.getCount(), stats.getSum(), stats.getAverage(), stats.getMax(), stats.getMin());
    });
}

自定义聚合(使用 reduce)

如果预定义的统计不满足需求,可以使用 reduce 进行自定义聚合。

1 找出薪资最高的人

public static void findMaxSalaryEmployee() {
    List<Employee> employees = Employee.getData();
    Optional<Employee> maxSalaryEmployee = employees.stream()
        .reduce((e1, e2) -> e1.getSalary() > e2.getSalary() ? e1 : e2);
    maxSalaryEmployee.ifPresent(e -> 
        System.out.println("薪资最高: " + e.getName() + " - " + e.getSalary()));
}

2 自定义:统计薪资高于平均工资的人数

public static void countAboveAverage() {
    List<Employee> employees = Employee.getData();
    // 先算平均工资
    double avgSalary = employees.stream()
        .mapToDouble(Employee::getSalary)
        .average()
        .orElse(0);
    // 再统计高于平均的人数
    long aboveAvg = employees.stream()
        .filter(e -> e.getSalary() > avgSalary)
        .count();
    System.out.println("平均薪资: " + avgSalary);
    System.out.println("高于平均薪资的人数: " + aboveAvg);
}

分组合并(将分组后的数据收集到 List)

有时候不仅要统计数字,还要保留分组后的原始数据。

public static void groupByDeptEmployees() {
    List<Employee> employees = Employee.getData();
    // 按部门分组,每个部门保留员工列表
    Map<String, List<Employee>> deptEmployees = employees.stream()
        .collect(Collectors.groupingBy(Employee::getDept));
    // 按部门分组,但只保留姓名
    Map<String, List<String>> deptNames = employees.stream()
        .collect(Collectors.groupingBy(
            Employee::getDept,
            Collectors.mapping(Employee::getName, Collectors.toList())
        ));
    System.out.println("各部门员工: " + deptNames);
    // 输出: {人事部=[孙八], 技术部=[张三, 李四, 钱七], 市场部=[王五, 赵六]}
}

综合案例:生成一份完整的统计报告

public static void generateReport() {
    List<Employee> employees = Employee.getData();
    // 1. 全公司统计
    DoubleSummaryStatistics companyStats = employees.stream()
        .collect(Collectors.summarizingDouble(Employee::getSalary));
    // 2. 部门统计
    Map<String, DoubleSummaryStatistics> deptStats = employees.stream()
        .collect(Collectors.groupingBy(
            Employee::getDept,
            Collectors.summarizingDouble(Employee::getSalary)
        ));
    // 3. 输出报告
    System.out.println("=== 公司薪资统计报告 ===");
    System.out.printf("员工总数: %d%n", companyStats.getCount());
    System.out.printf("薪资总额: %.2f%n", companyStats.getSum());
    System.out.printf("平均薪资: %.2f%n", companyStats.getAverage());
    System.out.printf("最高薪资: %.2f (由大佬获得)%n", companyStats.getMax());
    System.out.printf("最低薪资: %.2f%n%n", companyStats.getMin());
    System.out.println("=== 部门统计 ===");
    deptStats.forEach((dept, stats) -> {
        System.out.printf("【%s】人数: %d, 总薪资: %.0f, 平均: %.0f, 最高: %.0f, 最低: %.0f%n",
            dept, stats.getCount(), stats.getSum(), stats.getAverage(), stats.getMax(), stats.getMin());
    });
}

聚合方法速查表

功能 方法 示例
计数 counting() Collectors.counting()
求和 summingDouble() Collectors.summingDouble(Employee::getSalary)
平均 averagingDouble() Collectors.averagingDouble(Employee::getSalary)
综合统计 summarizingDouble() Collectors.summarizingDouble(Employee::getSalary)
分组计数 groupingBy(..., counting()) 按部门统计人数
分组求和 groupingBy(..., summingDouble()) 按部门统计薪资总和
分组多统计 groupingBy(..., summarizingDouble()) 按部门统计多个指标
自定义聚合 reduce() 找出薪资最高的人
分组保留数据 groupingBy(..., mapping(..., toList())) 按部门保留员工名单

最佳实践

  • 需要多个统计指标时,优先用 summarizingDouble()summarizingInt(),一次遍历完成所有计算。
  • 需要分组统计时,优先用 groupingBy + 下游收集器。
  • 复杂逻辑无法用内置收集器满足时,考虑 reduce 自定义。

抱歉,评论功能暂时关闭!