深入解析 Java Stream API:从 List 到 Map 的优雅转换!!!
🚀 深入解析 Java Stream API:从 List 到 Map 的优雅转换 🔧
大家好!👋 今天我们来聊聊 Java 8 中一个非常常见的操作:使用 Stream API 将 List 转换为 Map。🎉 具体来说,我们将深入分析以下代码片段:
Map<Integer, InviteCode> inviteCodeMap = inviteCodes.stream().collect(Collectors.toMap(InviteCode::getId, ic -> ic));
这段代码看似简单,但背后涉及了 Stream API、方法引用、Lambda 表达式以及 Collectors.toMap 的强大功能。💡 我们将从代码的背景开始,逐步拆解它的实现原理,探讨使用场景、优势和优化方法,最后通过一个实际案例展示它的应用。为了更直观地理解整个过程,我们还会插入一个 Mermaid 流程图!📊
准备好了吗?让我们开始吧!🚀
📖 背景:为什么需要将 List 转换为 Map?
在 Java 开发中,我们经常需要处理集合数据。例如,在一个邀请码系统中,我们有一个 List<InviteCode>,其中 InviteCode 是一个实体类,包含 id、inviteCode、inviteLevel 和 createdBy 等字段:
public class InviteCode {private Integer id;private String inviteCode;private Integer inviteLevel;private Integer createdBy;// Getters and Setterspublic Integer getId() {return id;}public String getInviteCode() {return inviteCode;}public Integer getInviteLevel() {return inviteLevel;}public Integer getCreatedBy() {return createdBy;}
}
假设我们有一个 List<InviteCode>,包含 adminId = 7 的所有邀请码记录:
| id | admin_id | created_by | invite_code | invite_level |
|---|---|---|---|---|
| 20 | 7 | NULL | ****** | 0 |
| 21 | 7 | 20 | 263113 | 1 |
| 22 | 7 | 20 | 704358 | 1 |
| 23 | 7 | 20 | 982868 | 1 |
| 24 | 7 | NULL | ****** | 0 |
| 25 | 7 | 24 | ****** | 1 |
| 26 | 7 | 25 | ****** | 2 |
| 27 | 7 | 26 | 991476 | 3 |
我们的目标是构建一个以 adminId 为根的邀请码层级树。为了高效地查找某个 id 对应的 InviteCode 对象,我们需要将 List<InviteCode> 转换为 Map<Integer, InviteCode>,其中:
- 键(Key):
InviteCode的id(Integer类型)。 - 值(Value):
InviteCode对象本身。
这就是以下代码的作用:
Map<Integer, InviteCode> inviteCodeMap = inviteCodes.stream().collect(Collectors.toMap(InviteCode::getId, ic -> ic));
🌟 代码拆解:一步步理解
让我们逐步拆解这段代码,弄清楚它是如何工作的!
1. inviteCodes.stream()
inviteCodes:是一个List<InviteCode>,包含adminId = 7的 8 条记录(id = 20, 21, ..., 27)。stream():将List<InviteCode>转换为一个Stream<InviteCode>。Stream是 Java 8 引入的流式 API,允许你以声明式的方式处理集合数据(例如映射、过滤、归约等)。
结果:inviteCodes.stream() 生成了一个 Stream<InviteCode>,包含 8 个 InviteCode 对象。
2. .collect(Collectors.toMap(...))
collect:是Stream的终止操作,用于将流中的元素收集到一个结果容器中(例如List、Set或Map)。Collectors.toMap:是一个收集器(Collector),专门用于将流中的元素收集到一个Map中。
Collectors.toMap 的方法签名
public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,Function<? super T, ? extends U> valueMapper
)
- 参数:
keyMapper:一个函数,用于从流中的每个元素提取Map的键(Key)。valueMapper:一个函数,用于从流中的每个元素提取Map的值(Value)。
- 返回值:一个
Map<K, U>。
在我们的代码中:
T是InviteCode(流中的元素类型)。K是Integer(键的类型)。U是InviteCode(值的类型)。
3. InviteCode::getId
InviteCode::getId:这是一个方法引用(Method Reference),等价于 Lambda 表达式ic -> ic.getId()。- 作用:从
InviteCode对象中提取id字段,作为Map的键。 - 类型:
Function<InviteCode, Integer>,将InviteCode映射为Integer。
示例:
- 如果
InviteCode对象的id是20,InviteCode::getId会返回20。
4. ic -> ic
ic -> ic:这是一个 Lambda 表达式,表示一个简单的映射函数。- 作用:将
InviteCode对象本身作为Map的值。 - 类型:
Function<InviteCode, InviteCode>,将InviteCode映射为它自己。
示例:
- 如果
InviteCode对象是ic(id = 20),ic -> ic直接返回这个ic对象。
5. 整体效果
Collectors.toMap(InviteCode::getId, ic -> ic):- 对
Stream<InviteCode>中的每个InviteCode对象:- 使用
InviteCode::getId提取id作为键。 - 使用
ic -> ic提取整个InviteCode对象作为值。
- 使用
- 将所有键值对收集到一个
Map<Integer, InviteCode>中。
- 对
结果:
inviteCodeMap是一个Map<Integer, InviteCode>,其中:inviteCodeMap.get(20):InviteCode(id=20, inviteCode="******", ...)。inviteCodeMap.get(21):InviteCode(id=21, inviteCode="263113", ...)。- …
inviteCodeMap.get(27):InviteCode(id=27, inviteCode="991476", ...)。
📊 Mermaid 流程图:可视化转换过程
为了更直观地理解 List<InviteCode> 到 Map<Integer, InviteCode> 的转换过程,我们使用 Mermaid 流程图来展示:
- 流程说明:
- 从
List<InviteCode>开始,转换为Stream<InviteCode>。 - 对流中的每个
InviteCode对象:- 使用
InviteCode::getId提取键(id)。 - 使用
ic -> ic提取值(InviteCode对象)。
- 使用
- 将所有键值对收集到
Map<Integer, InviteCode>中。
- 从
🌟 为什么需要 inviteCodeMap?
在邀请码系统中,我们的目标是构建一个以 adminId 为根的层级树。为了高效地查找某个 id 对应的 InviteCode 对象,我们需要将 List<InviteCode> 转换为 Map<Integer, InviteCode>。
1. 后续代码
// 找到所有根节点(createdBy = NULL)
List<InviteCode> roots = inviteCodes.stream().filter(ic -> ic.getCreatedBy() == null).collect(Collectors.toList());// 为每个根节点构建树形结构
List<InviteCodeTreeDTO> trees = new ArrayList<>();
for (InviteCode root : roots) {InviteCodeTreeDTO tree = buildTree(root, inviteCodeMap, new HashSet<>());trees.add(tree);
}
在 buildTree 方法中,需要根据 createdBy 查找子节点:
private InviteCodeTreeDTO buildTree(InviteCode root, Map<Integer, InviteCode> inviteCodeMap, Set<Integer> visited) {if (!visited.add(root.getId())) {throw new IllegalStateException("Detected a cycle in invite code hierarchy at ID: " + root.getId());}InviteCodeTreeDTO node = new InviteCodeTreeDTO();node.setId(root.getId());node.setInviteCode(root.getInviteCode());node.setInviteLevel(root.getInviteLevel());node.setChildren(new ArrayList<>());// 查找所有子节点(createdBy = root.id)List<InviteCode> children = inviteCodeMap.values().stream().filter(ic -> Objects.equals(ic.getCreatedBy(), root.getId())).collect(Collectors.toList());for (InviteCode child : children) {InviteCodeTreeDTO childNode = buildTree(child, inviteCodeMap, new HashSet<>(visited));node.getChildren().add(childNode);}return node;
}
- 为什么用
Map?- 如果直接遍历
inviteCodes查找子节点(createdBy = root.id),时间复杂度是O(n)。 - 使用
inviteCodeMap,可以通过id直接查找InviteCode对象,时间复杂度是O(1)(尽管当前children查找仍需优化)。
- 如果直接遍历
🚀 优势:为什么使用 Stream API?
1. 代码简洁
- Stream API 提供了声明式的写法,比传统的
for循环更简洁。 - 传统写法可能需要手动遍历和填充
Map:Map<Integer, InviteCode> inviteCodeMap = new HashMap<>(); for (InviteCode ic : inviteCodes) {inviteCodeMap.put(ic.getId(), ic); } - 使用 Stream API,代码更简洁优雅。
2. 功能强大
- Stream API 支持链式操作,可以轻松添加过滤、映射等操作。
- 例如,如果只想收集
inviteLevel > 0的InviteCode:Map<Integer, InviteCode> inviteCodeMap = inviteCodes.stream().filter(ic -> ic.getInviteLevel() > 0).collect(Collectors.toMap(InviteCode::getId, ic -> ic));
3. 并行处理
- Stream API 支持并行处理(
parallelStream()),在大规模数据下可以提高性能:Map<Integer, InviteCode> inviteCodeMap = inviteCodes.parallelStream().collect(Collectors.toMap(InviteCode::getId, ic -> ic));
🛠️ 优化建议
1. 更高效的子节点查找
当前 buildTree 方法中,查找子节点的方式可以通过 inviteCodeMap 进一步优化:
// 预先构建 createdBy 到子节点的映射
Map<Integer, List<InviteCode>> childrenMap = inviteCodes.stream().filter(ic -> ic.getCreatedBy() != null).collect(Collectors.groupingBy(InviteCode::getCreatedBy));// 修改 buildTree 方法
private InviteCodeTreeDTO buildTree(InviteCode root, Map<Integer, InviteCode> inviteCodeMap, Map<Integer, List<InviteCode>> childrenMap, Set<Integer> visited) {if (!visited.add(root.getId())) {throw new IllegalStateException("Detected a cycle in invite code hierarchy at ID: " + root.getId());}InviteCodeTreeDTO node = new InviteCodeTreeDTO();node.setId(root.getId());node.setInviteCode(root.getInviteCode());node.setInviteLevel(root.getInviteLevel());node.setChildren(new ArrayList<>());// 查找所有子节点(createdBy = root.id)List<InviteCode> children = childrenMap.getOrDefault(root.getId(), Collections.emptyList());for (InviteCode child : children) {InviteCodeTreeDTO childNode = buildTree(child, inviteCodeMap, childrenMap, new HashSet<>(visited));node.getChildren().add(childNode);}return node;
}
- 效果:通过
childrenMap,可以以O(1)的时间复杂度找到某个id的所有子节点。
2. 处理键冲突
Collectors.toMap 默认情况下,如果有重复的键(id),会抛出 IllegalStateException: Duplicate key。在我们的场景中,id 是主键,应该不会有重复,但为了安全起见,可以指定合并策略:
Map<Integer, InviteCode> inviteCodeMap = inviteCodes.stream().collect(Collectors.toMap(InviteCode::getId,ic -> ic,(existing, replacement) -> existing // 如果有重复的 id,保留第一个));
- 效果:如果
id重复,保留第一个InviteCode对象。
📝 完整代码:实际应用
以下是完整的 InviteCodeService 实现,展示了如何使用 inviteCodeMap 构建层级树:
public class InviteCodeService {private final InviteCodeRepository inviteCodeRepository;public InviteCodeService(InviteCodeRepository inviteCodeRepository) {this.inviteCodeRepository = inviteCodeRepository;}public AdminInviteCodeTreeDTO getAdminInviteCodeTree(Integer adminId) {List<InviteCode> inviteCodes = inviteCodeRepository.findByAdminId(adminId);if (inviteCodes.isEmpty()) {AdminInviteCodeTreeDTO result = new AdminInviteCodeTreeDTO();result.setAdminId(adminId);result.setChildren(Collections.emptyList());return result;}// 将 List<InviteCode> 转换为 Map<Integer, InviteCode>Map<Integer, InviteCode> inviteCodeMap = inviteCodes.stream().collect(Collectors.toMap(InviteCode::getId, ic -> ic));// 预构建 createdBy 到子节点的映射Map<Integer, List<InviteCode>> childrenMap = inviteCodes.stream().filter(ic -> ic.getCreatedBy() != null).collect(Collectors.groupingBy(InviteCode::getCreatedBy));// 找到所有根节点(createdBy = NULL)List<InviteCode> roots = inviteCodes.stream().filter(ic -> ic.getCreatedBy() == null).collect(Collectors.toList());// 为每个根节点构建树形结构List<InviteCodeTreeDTO> trees = new ArrayList<>();for (InviteCode root : roots) {InviteCodeTreeDTO tree = buildTree(root, inviteCodeMap, childrenMap, new HashSet<>());trees.add(tree);}AdminInviteCodeTreeDTO result = new AdminInviteCodeTreeDTO();result.setAdminId(adminId);result.setChildren(trees);return result;}private InviteCodeTreeDTO buildTree(InviteCode root, Map<Integer, InviteCode> inviteCodeMap, Map<Integer, List<InviteCode>> childrenMap, Set<Integer> visited) {if (!visited.add(root.getId())) {throw new IllegalStateException("Detected a cycle in invite code hierarchy at ID: " + root.getId());}InviteCodeTreeDTO node = new InviteCodeTreeDTO();node.setId(root.getId());node.setInviteCode(root.getInviteCode());node.setInviteLevel(root.getInviteLevel());node.setChildren(new ArrayList<>());List<InviteCode> children = childrenMap.getOrDefault(root.getId(), Collections.emptyList());for (InviteCode child : children) {InviteCodeTreeDTO childNode = buildTree(child, inviteCodeMap, childrenMap, new HashSet<>(visited));node.getChildren().add(childNode);}return node;}
}
🎉 总结
通过 Stream API 和 Collectors.toMap,我们可以轻松地将 List<InviteCode> 转换为 Map<Integer, InviteCode>,为后续的层级树构建提供了高效的数据结构。💻
- 核心代码:
inviteCodes.stream().collect(Collectors.toMap(InviteCode::getId, ic -> ic))将列表转换为映射。 - 优势:代码简洁、功能强大、支持并行处理。
- 优化:通过
childrenMap提高子节点查找效率,处理键冲突。
希望这篇博客对你理解 Stream API 和 Collectors.toMap 有所帮助!💬 如果你有其他问题,欢迎留言讨论!🚀
📚 参考:Java 官方文档、Collectors 源码。点赞和分享哦!😊

相关文章:
深入解析 Java Stream API:从 List 到 Map 的优雅转换!!!
🚀 深入解析 Java Stream API:从 List 到 Map 的优雅转换 🔧 大家好!👋 今天我们来聊聊 Java 8 中一个非常常见的操作:使用 Stream API 将 List 转换为 Map。🎉 具体来说,我们将深入…...
当全球化成为商业常态,Shopify 如何为品牌生意铺平出海之路?
从独立站搭建到支付履约,从数据分析到生态整合,Shopify 为不同规模的企业提供可扩展的解决方案。 在数字化浪潮的推动下,跨境电商与品牌出海的黄金时代已然到来。然而,看似广阔的市场蓝海背后,是无数企业正在经历的“成…...
集成平台是选择专业iPaaS厂商还是大型软件企业?
在数字化转型的浪潮中,企业对于高效、灵活的集成平台需求日益增长。iPaaS(Integration Platform as a Service)作为当下热门的解决方案,为企业提供了将不同应用和数据源进行整合的云端平台。面对市场上的众多选择,企业…...
RC6在线加密工具
RC6加密算法是一种基于RC5改进的分组密码算法,曾作为AES(高级加密标准)的候选算法之一。它采用了4个32位寄存器,增加了32位整数乘法运算,以增强扩散和混淆特性,提高了安全性。RC6的设计简单、高效ÿ…...
python每日十题(5)
保留字,也称关键字,是指被编程语言内部定义并保留使用的标识符。Python 3.x版本中有35个保留字,分别为:and, as,assert,async,await,break,class,continue,def,del,elif,else, except, False, finally,for,from,global, if,import…...
应用案例 | 核能工业:M-PM助力核工业科研项目
M-PM助力核工业科研项目 一、项目背景 在核工业复杂系统的研发进程中,MBSE(基于模型的系统工程)方法的应用愈发成熟,已然成为推动系统设计与优化的关键力量。如今,各相关设计系统的 MBSE 模型数据呈现出精细化、多元…...
通过 Executors 创建线程池
在Java中,使用线程池来管理和创建线程是一个更为高效和灵活的方法。线程池可以帮助你管理线程的生命周期,避免了频繁创建和销毁线程的开销,从而提高了性能。 Java 提供了java.util.concurrent包来处理线程池的相关操作。常用的线程池类是 Ex…...
4.1、网络安全模型
目录 网络安全体系概述网络安全模型-BLP模型网络安全模型-Biba模型网络安全模型 - 信息流模型信息保障模型能力成熟度模型其它安全模型网络安全原则 网络安全体系概述 网络安全体系是网络安全保证系统的最高层概念抽象,是一个体系,体系一般是一个概念&a…...
ManiWAV:通过野外的音频-视频数据学习机器人操作
24年6月来自斯坦福大学、哥伦比亚大学和 TRI 的论文“ManiWAV: Learning Robot Manipulation from In-the-Wild Audio-Visual Data”。 音频信号通过接触为机器人交互和物体属性提供丰富的信息。这些信息可以简化接触丰富的机器人操作技能学习,尤其是当视觉信息本身…...
可发1区的超级创新思路:基于注意力机制的DSD-CNN时间序列预测模型(功率预测、交通流量预测、故障检测)
首先声明,该模型为原创!原创!原创! 一、应用场景 该模型主要用于时间序列数据预测问题,包含功率预测、电池寿命预测、电机故障检测等等 二、模型整体介绍(本文以光伏功率预测为例) DSD-CNN(Depthwise-Spacewise Separable CNN)结合通道注意力机制,通过以下创新提升…...
IREE 调度机制深度解析:静态编译与动态执行的协同优化
IREE 调度机制深度解析:静态编译与动态执行的协同优化 一、引言 IREE (IR Execution Environment) 作为 TensorFlow 生态的重要成员,通过多层次调度策略实现了跨硬件平台的高效执行。其调度系统融合了编译期静态优化与运行时动态调整,在保证…...
istio 介绍-01-一个用于连接、管理和保护微服务的开放平台 概览
istio istio 一个用于连接、管理和保护微服务的开放平台。 介绍 Istio 是一个开放平台,用于提供统一的方式来集成微服务、管理跨微服务的流量、执行策略和聚合遥测数据。 Istio 的控制平面在底层集群管理平台(例如 Kubernetes)上提供了一…...
详细说明脚本评估和耗时较长的任务
在网页性能优化中,脚本评估和耗时较长的任务是两大关键性能瓶颈。它们直接影响页面的加载速度、交互响应以及用户体验。以下是对这两个概念的详细说明及优化策略: 一、脚本评估(Script Evaluation) 1. 定义 脚本评估指浏览器解析…...
Floyd 算法——97. 小明逛公园
卡码网:97. 小明逛公园https://kamacoder.com/problempage.php?pid=1155 题目描述 小明喜欢去公园散步,公园内布置了许多的景点,相互之间通过小路连接,小明希望在观看景点的同时,能够节省体力,走最短的路径。 给定一个公园景点图,图中有 N 个景点(编号为 1 到 N),…...
QT二 QT使用generate form 生成常用UI,各种UI控件
一 。没有使用general form 和 使用 general form 后,file层面和代码层面的不同比较 file层面的不同 代码层面的不同, 在 使用了general form之后,在主界面的构造方法中,使用ui->setupUi(this),就完成了所有UI的处理。 而之…...
蓝桥每日打卡--打家劫舍4
#蓝桥#JAVA#打家劫舍4 题目描述 沿街有一排连续的房屋。每间房屋内都藏有一定的现金。现在有一位小偷计划从这些房屋中窃取现金。 由于相邻的房屋装有相互连通的防盗系统,所以小偷 不会窃取相邻的房屋 。 小偷的 窃取能力 定义为他在窃取过程中能从单间房屋中窃…...
Intel Alder Lake N200桌面级处理器 详细介绍
1.Intel Alder Lake N200桌面级处理器 详细介绍 Intel Processor N200 是一款属于 Alder Lake-N 系列的入门级处理器,以下是其详细介绍: 基本规格 架构:Alder Lake-N,采用 Gracemont 架构的高效能核心。 核心与线程࿱…...
AudioTrack
AudioTrack是Android Audio系统提供给应用开发者(java/C)的API,用于操作音频播放的数据通路。MeidaPlayer在播放音乐时用到的是它,我们可以也可以直接使用AudioTrack进行音频播放。它是最基本的音频数据输出类。 AudioTrack.java…...
多条件排序(C# and Lua)
C# 升序排序 OrderBy 按升序对序列的元素进行排序 ThenBy 按升序对序列中的元素执行后续排序 降序排序 OrderByDescending 按降序对序列的元素排序 ThenByDescending 按降序对序列中的元素执行后续排序 public class Fruit {public int id;public string name;publi…...
人工智能之数学基础:线性方程组求解的得力助手——增广矩阵
本文重点 增广矩阵是一个极具实用价值的工具,尤其在处理线性方程组时,它展现了卓越的功效。通过整合系数和常数项,增广矩阵简化了计算过程并提供了判断方程组解集的有效方法。 增广矩阵的起源与定义 增广矩阵的概念源于线性方程组求解的需求。在解决线性方程组时,我们常…...
Vue3 + ECharts 数据可视化实战指南
一、为什么选择ECharts? 百度开源的成熟可视化库 支持30种图表类型 完善的文档和社区支持 与Vue3完美兼容 二、环境搭建 1. 创建Vue3项目 npm create vuelatest # 选择TypeScript、Pinia等按需配置 2. 安装核心依赖 npm install echarts vue-echarts vueus…...
关于Flask框架30道面试题及解析
文章目录 基础概念1. 什么是Flask?其核心特性是什么?2. Flask和Django的主要区别?3. 解释Flask中的“路由”概念。如何定义动态路由?核心组件4. Flask的请求上下文(Request Context)和应用上下文(Application Context)有什么区别?5. 如何访问请求参数?POST和GET方法的…...
服务安全认证概述与基础认证方式
文章目录 1. 引言1.1 认证与授权的区别1.2 认证方式的演进 2. 基础认证方式2.1 HTTP Basic Authentication2.2 API Key 认证2.3 HMAC-SHA256 签名认证2.4 JWT(JSON Web Token) 3. 认证方式对比与总结3.1 认证方式对比3.2 如何选择合适的认证方式…...
【Android Studio开发】生命周期、Activity和组件通信(上)
零、前期配置 1.【Android】模式 2.点击【运行】,弹出模拟器 右侧是模拟机,显示Hello World 3. 打开【activity_main.xml】文件,点击【Design】,然后点击【Component Tree】 在弹出的Component Tree中右键【main】,选择【Conver…...
【ES】Elasticsearch学习
文章目录 简单的安装 简单的安装 参考:https://blog.csdn.net/smilehappiness/article/details/118466378 官网:https://www.elastic.co/guide/en/elasticsearch/reference/current/targz.html 下载:https://www.elastic.co/cn/downloads/e…...
实验三 Python 数据可视化 Python 聚类-K-means(CQUPT)
一、实验目的 Python 数据可视化: 1、学习使用 jieba、wordcloud 等类库生成词云图。 2、学习使用 Matplotlib 库进行数据可视化。 Python 聚类-K-means: 1、理解聚类非监督学习方法的基本原理。 2、掌握 Python、numpy、pandas、sklearn 实现聚类…...
【STM32】SPI通信协议W25Q64Flash存储器芯片(学习笔记)
通信接口部分有介绍SPI:【STM32】USART串口协议&串口外设-学习笔记-CSDN博客 SPI通信协议 SPI通信 SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线四根通信线:SCK(Serial Clock&…...
JavaScript实现一个函数,将数组扁平化(flatten),即把多维数组转为一维数组。
大白话实现一个函数,将数组扁平化(flatten),即把多维数组转为一维数组。 思路 实现数组扁平化的基本思路是遍历数组中的每个元素,如果元素是数组,就递归地将其扁平化并添加到结果数组中;如果元…...
SpringBoot最佳实践之 - 使用AOP记录操作日志
1. 前言 本篇博客是个人在工作中遇到的需求。针对此需求,开发了具体的实现代码。并不是普适的记录操作日志的方式。以阅读本篇博客的朋友,可以参考此篇博客中记录日志的方式,可能会对你有些许帮助和启发。 2. 需求描述 有一个后台管理系统…...
第六届机电一体化技术与智能制造国际学术会议(ICMTIM 2025)
重要信息 4月11-13日 南京江北新区工业大学亚朵酒店 www.icmtim.org(点击了解参会投稿等) 简介 由南京工业大学主办,南京工业大学电气工程与控制科学学院、中国矿业大学、黑龙江大学、江苏省自动化学会承办的第六届机电一体化技术…...
