首页 > Java > java教程 > 正文

Java Stream API:高效处理学生成绩数据并按平均分排序教程

聖光之護
发布: 2025-07-16 11:18:49
原创
124人浏览过

Java Stream API:高效处理学生成绩数据并按平均分排序教程

本教程详细介绍了如何使用Java Stream API高效处理学生成绩数据。内容涵盖从数据收集、利用Collectors.toMap将学生多门成绩转换为平均分、到使用流操作进行过滤、以及最终通过Map.Entry.comparingByValue进行降序排序并格式化输出。通过优化计算逻辑和利用Stream API的强大功能,避免了重复计算,提升了代码的简洁性和执行效率。

1. 问题背景与数据处理需求

在实际应用中,我们经常需要处理结构化数据,例如学生成绩。本教程的目标是构建一个程序,能够:

  1. 读取指定数量的学生姓名及其对应的多门成绩。
  2. 记录每位学生的全部成绩。
  3. 计算每位学生的平均分。
  4. 筛选出平均分高于或等于4.50的学生。
  5. 将筛选后的学生按照平均分降序排列。
  6. 以特定格式("{姓名} -> {平均分}",平均分保留两位小数)输出结果。

2. 初始数据收集与存储

首先,我们需要一个数据结构来存储学生的姓名和他们的多门成绩。HashMap是一个理想的选择,其中键为学生姓名(String),值为一个包含该学生所有成绩的列表(List)。

import java.util.*;
import java.util.stream.Collectors;

public class StudentGradesProcessor {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        int n = Integer.parseInt(scanner.nextLine()); // 读取学生数量

        Map<String, List<Double>> studentRecords = new HashMap<>();

        // 循环读取学生姓名和成绩
        while (n > 0) {
            String name = scanner.nextLine();
            double grade = Double.parseDouble(scanner.nextLine());
            // 如果学生不存在,则添加新条目;否则,将成绩添加到现有列表中
            studentRecords.putIfAbsent(name, new ArrayList<>());
            studentRecords.get(name).add(grade);
            n--;
        }

        // 后续处理将在下方详细介绍
    }
}
登录后复制

在上述代码中,我们使用putIfAbsent方法确保每个学生姓名只创建一次列表,然后通过get(name).add(grade)将成绩添加到对应的列表中。

3. Stream API优化:计算平均分、过滤与排序

原始的处理方式可能会在过滤和排序阶段多次计算学生的平均分,这不仅效率低下,而且在进行浮点数比较时,直接将double类型转换为int进行排序(如(int) (average2 - average1))可能会导致精度丢失,从而产生错误的排序结果。

为了解决这些问题,我们可以采用以下优化策略:

立即学习Java免费学习笔记(深入)”;

  1. 一次性计算平均分并存储: 将Map>转换为Map,其中键是学生姓名,值是其计算好的平均分。
  2. 利用Map.Entry的比较器: Java 8的Stream API提供了强大的Comparator辅助方法,特别是针对Map.Entry的排序,可以避免手动编写复杂的比较逻辑。

3.1 转换数据结构:计算平均分

我们可以使用Collectors.toMap将原始的studentRecords映射转换为一个新的Map,其中存储的是学生的姓名及其平均分。

        Map<String, Double> recordsWithAverage = studentRecords.entrySet()
            .stream()
            // 将每个Map.Entry<String, List<Double>>转换为Map.Entry<String, Double>
            // 键保持不变,值为对应List<Double>的平均值
            .collect(Collectors.toMap(
                Map.Entry::getKey, // 新Map的键是原始Map的键(学生姓名)
                e -> e.getValue().stream().mapToDouble(Double::doubleValue).average().orElse(0.0) // 新Map的值是平均分
            ));
登录后复制

这里,e -> e.getValue().stream().mapToDouble(Double::doubleValue).average().orElse(0.0)负责计算每个学生所有成绩的平均值。mapToDouble(Double::doubleValue)将Stream转换为DoubleStream,以便进行平均值计算。orElse(0.0)用于处理学生没有成绩的情况(尽管在此问题中不会发生,但作为健壮性考虑是好的实践)。

3.2 过滤与排序

现在我们有了recordsWithAverage,其中每个学生都对应一个已经计算好的平均分。接下来的过滤和排序操作将变得非常简洁和高效。

        recordsWithAverage.entrySet()
            .stream()
            // 过滤:只保留平均分大于或等于4.50的学生
            .filter(e -> e.getValue() >= 4.50)
            // 排序:按平均分降序排列
            // Map.Entry.comparingByValue() 返回一个Comparator,用于比较Map.Entry的值
            // Comparator.reverseOrder() 将比较器反转,实现降序排列
            .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
            // 遍历并打印结果
            .forEach(pair -> {
                System.out.printf("%s -> %.2f%n", pair.getKey(), pair.getValue());
            });
登录后复制

这里的关键是Map.Entry.comparingByValue(Comparator.reverseOrder())。它直接作用于Map.Entry对象,根据其值进行比较,并且Comparator.reverseOrder()确保了降序排列。这样,我们无需手动编写复杂的Comparator逻辑,代码更加简洁易懂。

4. 完整代码示例

将上述所有部分整合起来,完整的Java程序如下:

import java.util.*;
import java.util.stream.Collectors;

public class StudentGradesProcessor {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        int n = Integer.parseInt(scanner.nextLine());

        // 步骤1: 收集原始学生姓名和成绩数据
        Map<String, List<Double>> studentRecords = new HashMap<>();
        while (n > 0) {
            String name = scanner.nextLine();
            double grade = Double.parseDouble(scanner.nextLine());
            studentRecords.putIfAbsent(name, new ArrayList<>());
            studentRecords.get(name).add(grade);
            n--;
        }

        // 步骤2: 计算每个学生的平均分,并存储到新的Map中
        Map<String, Double> recordsWithAverage = studentRecords.entrySet()
            .stream()
            .collect(Collectors.toMap(
                Map.Entry::getKey,
                e -> e.getValue().stream().mapToDouble(Double::doubleValue).average().orElse(0.0)
            ));

        // 步骤3: 过滤、排序并打印结果
        recordsWithAverage.entrySet()
            .stream()
            .filter(e -> e.getValue() >= 4.50) // 过滤:平均分大于等于4.50
            .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())) // 排序:按平均分降序
            .forEach(pair -> {
                System.out.printf("%s -> %.2f%n", pair.getKey(), pair.getValue()); // 格式化输出
            });

        scanner.close(); // 关闭Scanner
    }
}
登录后复制

5. 注意事项与总结

  • 避免重复计算: 将平均分计算为独立的一步,避免在过滤和排序过程中反复计算,显著提高了效率。
  • 使用Double.compare()或内置比较器: 对于浮点数比较,应使用Double.compare()或像Map.Entry.comparingByValue()这样的内置比较器,而不是将差值强制转换为int,以避免精度问题和不正确的排序结果。
  • Stream API的链式操作: Stream API允许将多个操作(如filter、sorted、forEach)链式调用,使代码更具可读性和表达力。
  • Collectors.toMap的强大功能: Collectors类提供了多种强大的收集器,能够将流中的元素转换成不同的数据结构,是Stream API中不可或缺的一部分。
  • 资源管理: 养成关闭Scanner等资源的好习惯,避免资源泄露。

通过本教程,您应该已经掌握了如何使用Java Stream API高效地处理学生成绩数据,包括数据的收集、转换、过滤和排序,并了解了如何优化代码以提高性能和准确性。

以上就是Java Stream API:高效处理学生成绩数据并按平均分排序教程的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号