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

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

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

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

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

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

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> 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免费学习笔记(深入)”;

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

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

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

序列猴子开放平台 序列猴子开放平台

具有长序列、多模态、单模型、大数据等特点的超大规模语言模型

序列猴子开放平台 0 查看详情 序列猴子开放平台

        Map recordsWithAverage = studentRecords.entrySet()            .stream()            // 将每个Map.Entry<String, List>转换为Map.Entry            // 键保持不变,值为对应List的平均值            .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> 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 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:高效处理学生成绩数据并按平均分排序教程的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/344132.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月5日 19:37:30
下一篇 2025年11月5日 19:39:38

相关推荐

  • Mongoose关联查询:通过引用文档的名称字段检索数据

    本文详细介绍了在Mongoose中如何通过引用文档的非ID字段(如分类名称)来检索主文档(如产品)。核心方法是分两步进行:首先根据名称查找引用文档的ID,然后使用该ID来查询主文档。文章还探讨了如何设计Schema以支持单类别或多类别引用,并提供了相应的代码示例和注意事项。 理解Mongoose引用…

    2025年12月20日
    000
  • 如何用Web Serial API与串口设备通信?

    Web Serial API允许网页通过浏览器直接与串口设备通信,需用户点击触发连接,使用requestPort选择设备并设置正确波特率(如9600),通过readable流持续读取Uint8Array数据并解码,利用writable流发送格式化指令(如添加换行符),同时监听disconnect事件…

    2025年12月20日
    000
  • 如何构建一个支持语法高亮、自动补全的在线代码编辑器?

    答案:构建在线代码编辑器应选用Monaco Editor,它支持语法高亮与自动补全,通过npm安装并初始化编辑器实例,设置language属性启用语法高亮,结合monaco-languageclient集成LSP实现智能补全,前后端可通过WebSocket通信,适合类IDE开发场景。 要构建一个支持…

    2025年12月20日
    000
  • 如何利用 Service Worker 实现可靠的离线应用和资源缓存?

    Service Worker 是实现 Web 应用离线可用的核心,通过注册并激活代理、缓存关键资源、拦截请求返回缓存内容,并在更新时清理旧缓存,确保离线体验稳定可靠。 要让 Web 应用在离线状态下依然可用,Service Worker 是核心工具。它充当浏览器与网络之间的代理,能拦截请求并返回缓存…

    2025年12月20日
    000
  • 如何利用 JavaScript 实现一个简单的区块链数据结构?

    区块包含索引、时间戳、数据、前一区块哈希和自身哈希,通过SHA-256计算;2. 区块链由区块链接构成,首块为创世块;3. 验证链完整性需检查每块哈希及前后连接一致性。 要实现一个简单的区块链数据结构,核心是理解区块链的基本组成:区块、哈希计算和链式连接。下面用 JavaScript 实现一个基础版…

    2025年12月20日
    000
  • 如何实现一个虚拟列表(Virtual List)以优化长列表渲染性能?

    虚拟列表通过只渲染可视区域元素来减少DOM数量,提升长列表性能。1. 核心原理:根据滚动位置、容器尺寸和项高度计算可见范围,用占位模拟整体高度并复用节点。2. 实现结构:定义itemHeight、containerHeight、scrollTop等变量,通过Math.floor(scrollTop …

    2025年12月20日
    000
  • 如何利用 JavaScript 的 Service Worker 实现离线可用的 Web 应用?

    Service Worker通过拦截请求和缓存资源实现离线访问,需在HTTPS环境下注册sw.js文件;安装时预缓存核心资源,激活后采用缓存优先策略响应请求,并在版本更新时清理旧缓存,从而提升Web应用的离线可用性。 要让 Web 应用在离线状态下依然可用,Service Worker 是关键。它是…

    2025年12月20日
    000
  • JavaScript中的CSS Houdini如何突破样式限制?

    CSS Houdini通过Typed OM、自定义属性、Paint Worklet和Animation Worklet让JS深度参与样式布局,1. Typed OM提升样式操作安全性与计算能力;2. 自定义属性支持继承与动画;3. Paint Worklet实现高性能动态绘图;4. Animatio…

    2025年12月20日
    000
  • 如何利用机器学习库(如TensorFlow.js)在浏览器中运行AI模型?

    答案:在浏览器中运行AI模型需将模型转为TensorFlow.js格式,通过异步加载、输入预处理和predict推理实现,结合WebGL加速与内存优化提升性能。 在浏览器中运行AI模型已成为前端智能化的重要方向,借助TensorFlow.js这类机器学习库,开发者可以直接在网页中加载和执行训练好的模…

    2025年12月20日
    000
  • 使用 CSS Grid 实现父容器高度自适应子内容高度

    本文介绍如何使用 CSS Grid 布局来实现父容器的高度自适应其子内容的高度,即使子内容可能超出视口范围,且无需使用 JavaScript。通过将父容器设置为 Grid 容器,并将背景和文本内容都放置在同一行和列中,可以轻松实现背景高度与文本内容高度一致的效果。 使用 CSS Grid 实现高度自…

    2025年12月20日
    000
  • 解决点击按钮时元素跳动问题的CSS对齐技巧

    本文探讨了在网页开发中,点击按钮时元素发生跳动的问题,特别是当按钮状态切换导致CSS属性(如border-style和padding)变化时。通过深入分析其根本原因——内联元素基线对齐和盒模型变化,文章提供了使用vertical-align: middle;这一CSS属性的解决方案,确保按钮及其周围…

    2025年12月20日
    000
  • 如何利用JavaScript操作二进制数据,如ArrayBuffer和Blob?

    ArrayBuffer是二进制数据容器,需通过TypedArray或DataView视图操作;Blob用于封装文件类二进制对象,可与ArrayBuffer互转,适用于文件传输与网络通信。 JavaScript 提供了多种方式来操作二进制数据,特别是在处理文件、网络传输、图像音频处理等场景中,Arra…

    2025年12月20日
    000
  • ElectronJS中渲染进程调用主进程多线程函数的IPC实践

    ElectronJS教程:本文详细介绍了如何在ElectronJS应用中,通过渲染进程安全有效地调用主进程中包含多线程逻辑的函数。核心在于利用Electron的ipcRenderer和ipcMain模块建立进程间通信,允许渲染进程发送请求,主进程接收并执行基于threads.js的异步任务,从而实现…

    2025年12月20日
    000
  • 使用 CSS Grid 实现父容器高度与内容自适应

    本文介绍如何使用 CSS Grid 布局,在不使用 JavaScript 的情况下,使父容器的高度自动适应其内容的高度,即使内容超出视口也能正确撑开父容器。通过将父容器设置为 Grid 布局,并将其子元素置于同一行和列,可以轻松实现背景与内容高度的完美匹配。 在网页开发中,经常会遇到需要让背景元素的…

    2025年12月20日
    000
  • 如何用Web Locks API管理资源并发访问?

    Web Locks API 是一种浏览器提供的机制,通过互斥锁协调同源下页面与 Worker 对共享资源的访问。它不锁定硬件资源,而是提供逻辑同步,确保关键代码串行执行,避免竞态条件。核心方法为 navigator.locks.request(lockName, options?, callback…

    2025年12月20日
    000
  • 将扁平JSON数据转换为带层级的嵌套结构

    本教程详细介绍了如何将包含层级(level)信息的扁平JSON数组转换为具有父子关系的嵌套JSON结构。通过迭代数据并利用映射表追踪各层级节点,我们可以高效地构建出复杂的树状结构,适用于动态菜单、文件系统表示等场景,确保输出结构清晰、逻辑严谨。 1. 场景概述与问题定义 在前端开发或数据处理中,我们…

    2025年12月20日
    000
  • Highcharts浮动条形图(范围条形图)实现指南

    本教程详细介绍了如何在Highcharts中创建浮动条形图,即水平方向的范围条形图。通过利用Highcharts的bar系列类型,并结合数据点的y(高值)和low(低值)属性,可以轻松实现指定左右边界的条形图效果,克服了columnrange仅支持垂直方向的限制。 理解浮动条形图的需求 在数据可视化…

    2025年12月20日
    000
  • Nightwatch.js中高效管理元素选择器:告别重复定义

    本教程探讨Nightwatch.js中避免重复使用元素选择器的方法。针对在同一元素上执行多项操作时选择器冗余的问题,文章提供了两种核心解决方案:通过常量变量复用选择器,以及利用页面对象(Page Objects)进行集中管理。同时,教程也解释了Nightwatch.js与Cypress在命令链式调用…

    2025年12月20日
    000
  • iFrame交互后页面滚动位置自动恢复教程:利用URL变化监听与自定义事件

    本教程旨在解决iFrame内操作导致主页面URL更新及滚动位置重置的问题。我们将探讨从最初的基于加载事件的尝试,到通过实时监控URL变化来触发滚动,最终引入更优雅的自定义事件和哈希变化监听机制,确保用户在iFrame内容更新后能自动回到正确视图,提升用户体验。 理解问题:iFrame交互与页面滚动重…

    2025年12月20日
    000
  • 优化React表单输入与API请求:useEffect的正确姿势与提交处理

    本文深入探讨了React函数组件中处理表单输入与API请求的常见陷阱,特别是useEffect钩子的不当使用。我们将通过一个实际案例,详细阐述useEffect应如何正确放置、表单如何有效提交以及如何将用户输入与API调用逻辑无缝集成,以避免不必要的页面刷新和渲染问题,确保数据正确加载。 引言 在r…

    2025年12月20日
    000

发表回复

登录后才能评论
关注微信