JavaStream多表排序技巧分享
本文深入解析**Java Stream**在多表聚合排序中的实战技巧,聚焦如何利用Stream API高效处理关联数据,实现复杂的数据筛选、聚合与排序。针对实际应用中常见的挑战,例如从电影评分数据中找出平均分最高的N部电影并按预算排序,本文将提供详细的步骤和示例代码。通过创建电影ID映射、计算平均分、筛选Top N电影以及二次排序等关键步骤,全面展示**Java Stream API**在内存数据处理方面的强大能力和灵活实践。掌握这些技巧,助力开发者更高效地处理复杂数据,提升Java应用的性能与可维护性。

背景与挑战
在实际应用中,我们经常需要处理来自不同来源但逻辑关联的数据。例如,给定用户、电影和评分三张表的数据,我们可能需要找出平均评分最高的电影,并在此基础上根据其他属性(如预算)进行排序。传统的关系型数据库可以通过复杂的SQL查询轻松实现这一目标。然而,当数据已经在内存中以对象集合的形式存在时,如何利用Java的强大功能,尤其是Stream API,以声明式、高效的方式完成此类复杂的数据操作,是许多开发者面临的挑战。
本教程将以一个具体场景为例:从电影评分数据中,首先找出平均分最高的5部电影,然后将这5部电影按照预算从高到低进行排序。
数据模型构建
为了模拟实际场景,我们首先定义所需的数据模型。Java 16及更高版本引入的record类型非常适合这种不可变数据载体的定义,它简洁且功能强大。
// 电影评分记录
record Score(int userId, int movieId, int score) {}
// 电影信息记录
record Movie(int id, String name, int budget) {}接下来,我们创建一些示例数据来演示:
List<Movie> movies = List.of(
new Movie(101, "Mov 1", 200),
new Movie(102, "Mov 2", 500),
new Movie(103, "Mov 3", 300),
new Movie(104, "Mov 4", 450),
new Movie(105, "Mov 5", 600),
new Movie(106, "Mov 6", 150)
);
List<Score> scores = List.of(
new Score(1, 101, 7),
new Score(2, 101, 8),
new Score(1, 102, 6),
new Score(2, 102, 9),
new Score(1, 103, 8),
new Score(2, 103, 7),
new Score(1, 104, 9),
new Score(2, 104, 8),
new Score(1, 105, 7),
new Score(2, 105, 7),
new Score(1, 106, 5),
new Score(2, 106, 6)
);核心逻辑:Java Stream 实现步骤
我们将通过一系列Stream操作来逐步实现目标。
步骤1:数据准备与辅助映射
为了高效地将电影ID映射回完整的Movie对象,我们首先创建一个Map
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
Map<Integer, Movie> movieMap = movies.stream()
.collect(Collectors.toMap(Movie::id, Function.identity()));步骤2:计算电影平均分
接下来,我们需要计算每部电影的平均分。这可以通过scores列表的Stream操作,结合Collectors.groupingBy和Collectors.averagingDouble来实现。groupingBy将所有评分按movieId分组,而averagingDouble则计算每个组的平均分。
import java.util.Map.Entry;
import java.util.Collections;
// ... (previous code)
Map<Integer, Double> movieAverageScores = scores.stream()
.collect(Collectors.groupingBy(
Score::movieId,
Collectors.averagingDouble(Score::score)
));此时,movieAverageScores将是一个Map
步骤3:筛选平均分最高的电影
从平均分Map中,我们需要找出平均分最高的5部电影。这需要将Map的entrySet()转换为Stream,然后进行排序和限制。
// ... (previous code)
List<Movie> top5MoviesByScore = movieAverageScores.entrySet().stream()
// 按平均分降序排序
.sorted(Collections.reverseOrder(Entry.comparingByValue()))
// 限制为前5个
.limit(5)
// 将Map.Entry的Key(movieId)映射回Movie对象
.map(e -> movieMap.get(e.getKey()))
.toList(); // 收集为List,此时已经获取了前5部电影,但尚未按预算排序注意:movieMap.get(e.getKey())这里假设所有评分中的movieId都能在movies列表中找到。在实际应用中,可能需要增加空值检查。
步骤4:关联电影信息并二次排序
现在我们已经得到了平均分最高的5部电影(top5MoviesByScore),但它们还未按预算排序。我们需要对这个列表进行第二次排序,这次是根据电影的budget字段进行降序排列。
import java.util.Comparator;
// ... (previous code)
List<Movie> finalTop5SortedByBudget = top5MoviesByScore.stream()
// 按预算降序排序
.sorted(Collections.reverseOrder(Comparator.comparing(Movie::budget)))
.toList();或者,我们可以将步骤3和步骤4合并,形成一个更长的链式操作:
// ... (previous code)
List<Movie> finalTop5SortedByBudget = movieAverageScores.entrySet().stream()
.sorted(Collections.reverseOrder(Entry.comparingByValue())) // 1. 按平均分降序
.limit(5) // 2. 取前5个
.map(e -> movieMap.get(e.getKey())) // 3. 映射到Movie对象
.sorted(Collections.reverseOrder(Comparator.comparing(Movie::budget))) // 4. 按预算降序
.toList(); // 5. 收集结果完整示例代码
将以上所有步骤整合到一个可运行的main方法中:
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import java.util.stream.Collectors;
public class MovieAnalysis {
// 电影评分记录
record Score(int userId, int movieId, int score) {}
// 电影信息记录
record Movie(int id, String name, int budget) {}
public static void main(String[] args) {
// 示例电影数据
List<Movie> movies = List.of(
new Movie(101, "Mov 1", 200),
new Movie(102, "Mov 2", 500),
new Movie(103, "Mov 3", 300),
new Movie(104, "Mov 4", 450),
new Movie(105, "Mov 5", 600),
new Movie(106, "Mov 6", 150)
);
// 示例评分数据
List<Score> scores = List.of(
new Score(1, 101, 7),
new Score(2, 101, 8), // Avg 101: 7.5
new Score(1, 102, 6),
new Score(2, 102, 9), // Avg 102: 7.5
new Score(1, 103, 8),
new Score(2, 103, 7), // Avg 103: 7.5
new Score(1, 104, 9),
new Score(2, 104, 8), // Avg 104: 8.5
new Score(1, 105, 7),
new Score(2, 105, 7), // Avg 105: 7.0
new Score(1, 106, 5),
new Score(2, 106, 6) // Avg 106: 5.5
);
// 步骤1: 创建电影ID到电影对象的映射,用于高效查找
Map<Integer, Movie> movieMap = movies.stream()
.collect(Collectors.toMap(Movie::id, Function.identity()));
// 步骤2-5: 综合Stream操作
List<Movie> top5MoviesSortedByBudget = scores.stream()
// 2. 按movieId分组并计算平均分
.collect(Collectors.groupingBy(
Score::movieId,
Collectors.averagingDouble(Score::score)))
// 将Map的EntrySet转换为Stream,以便排序和筛选
.entrySet().stream()
// 3. 按平均分降序排序
.sorted(Collections.reverseOrder(Entry.comparingByValue()))
// 3. 限制为前5个
.limit(5)
// 4. 将movieId映射回Movie对象
.map(e -> movieMap.get(e.getKey()))
// 4. 对这前5部电影按预算降序排序
.sorted(Collections.reverseOrder(Comparator.comparing(Movie::budget)))
// 5. 收集结果
.toList();
// 打印结果
System.out.println("平均分最高且按预算排序的前5部电影:");
top5MoviesSortedByBudget.forEach(System.out::println);
}
}运行输出示例: 根据示例数据,电影的平均分如下: Mov 1 (101): 7.5 Mov 2 (102): 7.5 Mov 3 (103): 7.5 Mov 4 (104): 8.5 Mov 5 (105): 7.0 Mov 6 (106): 5.5
平均分最高的前5部电影(以及它们的预算):
- Mov 4 (104): 8.5分, 预算 450
- Mov 1 (101): 7.5分, 预算 200
- Mov 2 (102): 7.5分, 预算 500
- Mov 3 (103): 7.5分, 预算 300
- Mov 5 (105): 7.0分, 预算 600
当平均分相同时,Stream的sorted操作可能会保持原有顺序(取决于具体实现),但我们最终会根据预算进行二次排序。 按照预算降序排序后: Movie[id=105, name=Mov 5, budget=600] (平均分 7.0) Movie[id=102, name=Mov 2, budget=500] (平均分 7.5) Movie[id=104, name=Mov 4, budget=450] (平均分 8.5) Movie[id=103, name=Mov 3, budget=300] (平均分 7.5) Movie[id=101, name=Mov 1, budget=200] (平均分 7.5)
注意:在平均分相同的情况下,limit(5)会选择哪些电影,这可能取决于它们在entrySet()中的迭代顺序。如果需要更严格的同分处理规则(例如,同分时按预算降序,然后再取前5),则需要调整排序逻辑,在第一次排序时就考虑预算。不过,当前的需求是“先取平均分最高的5部,再对这5部按预算排序”,所以上述实现是符合的。
注意事项与最佳实践
- 数据量考量: Java Stream API主要适用于内存中的数据处理。对于超大规模数据(例如,数亿条记录),将所有数据加载到内存中可能会导致内存溢出。在这种情况下,数据库查询(SQL)或大数据处理框架(如Spark)是更合适的选择。
- 性能优化: 创建movieMap是一个重要的优化点。如果没有这个映射,每次需要Movie对象时都去遍历movies列表,会大大降低性能。
- 空值处理: 在map(e -> movieMap.get(e.getKey()))这一步,如果scores中存在movieId但在movies列表中不存在的情况,movieMap.get()将返回null。这可能导致NullPointerException。在生产代码中,应考虑Optional或过滤掉这些无效的条目。
- 可读性: 尽管Stream API支持链式调用,但过长的链式操作有时会降低代码的可读性。适当地拆分成多个变量或方法,可以提高代码的清晰度。
- record的优势: record类型自动提供了equals(), hashCode(), toString()以及构造函数和访问器方法,极大地简化了数据模型的定义,并使其不可变,减少了潜在的错误。
总结
通过本教程,我们深入探讨了如何利用Java Stream API处理多表关联数据,并实现了复杂的数据聚合、筛选和二次排序逻辑。从数据模型构建、计算平均分、筛选Top N,到最终的二次排序,Java Stream API提供了一种强大、声明式且高效的方式来操作内存中的数据集合。掌握这些技巧,将有助于开发者更灵活、更优雅地应对各种复杂的数据处理需求。
好了,本文到此结束,带大家了解了《JavaStream多表排序技巧分享》,希望本文对你有所帮助!关注golang学习网公众号,给大家分享更多文章知识!
微信收款码申请教程:个人开通收款步骤详解
- 上一篇
- 微信收款码申请教程:个人开通收款步骤详解
- 下一篇
- RunwayML动态艺术教程详解
-
- 文章 · java教程 | 5分钟前 |
- Java断言assert用法详解
- 479浏览 收藏
-
- 文章 · java教程 | 8分钟前 |
- JavaStream快速找两数之和技巧
- 345浏览 收藏
-
- 文章 · java教程 | 29分钟前 |
- Java链表节点与引用管理详解
- 203浏览 收藏
-
- 文章 · java教程 | 53分钟前 |
- JavaSocket编程实战教程
- 357浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Java十六进制转二进制保留零方法
- 166浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- JavaIOException常见问题与解决方法
- 428浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- final关键字的作用及使用场景
- 444浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- SpringSecurity配置H2数据库控制台步骤
- 434浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- OpenSearch字段Terms查询无结果解决方法
- 116浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Java长期稳定运行优化方案
- 445浏览 收藏
-
- 文章 · java教程 | 2小时前 | 排序 集合 Lambda表达式 comparator List.sort
- JavaLambda排序实战教程
- 197浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3182次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3393次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3425次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4530次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3802次使用
-
- 提升Java功能开发效率的有力工具:微服务架构
- 2023-10-06 501浏览
-
- 掌握Java海康SDK二次开发的必备技巧
- 2023-10-01 501浏览
-
- 如何使用java实现桶排序算法
- 2023-10-03 501浏览
-
- Java开发实战经验:如何优化开发逻辑
- 2023-10-31 501浏览
-
- 如何使用Java中的Math.max()方法比较两个数的大小?
- 2023-11-18 501浏览

