深入解析 Java Stream API:从 List 到 Map 的优雅转换!!!

news2025/3/23 17:41:02

🚀 深入解析 Java Stream API:从 ListMap 的优雅转换 🔧

大家好!👋 今天我们来聊聊 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 是一个实体类,包含 idinviteCodeinviteLevelcreatedBy 等字段:

public class InviteCode {
    private Integer id;
    private String inviteCode;
    private Integer inviteLevel;
    private Integer createdBy;

    // Getters and Setters
    public Integer getId() {
        return id;
    }

    public String getInviteCode() {
        return inviteCode;
    }

    public Integer getInviteLevel() {
        return inviteLevel;
    }

    public Integer getCreatedBy() {
        return createdBy;
    }
}

假设我们有一个 List<InviteCode>,包含 adminId = 7 的所有邀请码记录:

idadmin_idcreated_byinvite_codeinvite_level
207NULL******0
217202631131
227207043581
237209828681
247NULL******0
25724******1
26725******2
277269914763

我们的目标是构建一个以 adminId 为根的邀请码层级树。为了高效地查找某个 id 对应的 InviteCode 对象,我们需要将 List<InviteCode> 转换为 Map<Integer, InviteCode>,其中:

  • 键(Key)InviteCodeidInteger 类型)。
  • 值(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 的终止操作,用于将流中的元素收集到一个结果容器中(例如 ListSetMap)。
  • 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>

在我们的代码中:

  • TInviteCode(流中的元素类型)。
  • KInteger(键的类型)。
  • UInviteCode(值的类型)。

3. InviteCode::getId

  • InviteCode::getId:这是一个方法引用(Method Reference),等价于 Lambda 表达式 ic -> ic.getId()
  • 作用:从 InviteCode 对象中提取 id 字段,作为 Map 的键。
  • 类型Function<InviteCode, Integer>,将 InviteCode 映射为 Integer

示例

  • 如果 InviteCode 对象的 id20InviteCode::getId 会返回 20

4. ic -> ic

  • ic -> ic:这是一个 Lambda 表达式,表示一个简单的映射函数。
  • 作用:将 InviteCode 对象本身作为 Map 的值。
  • 类型Function<InviteCode, InviteCode>,将 InviteCode 映射为它自己。

示例

  • 如果 InviteCode 对象是 icid = 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 流程图来展示:

Start: List<InviteCode>
inviteCodes
Stream<InviteCode>
inviteCodes.stream()
For each InviteCode in Stream
Extract Key:
InviteCode::getId
(e.g., id = 20)
Extract Value:
ic -> ic
(e.g., InviteCode object)
Key-Value Pair:
(20, InviteCode(id=20, ...))
Collect to Map<Integer, InviteCode>
Collectors.toMap()
End: Map<Integer, InviteCode>
inviteCodeMap
  • 流程说明
    1. List<InviteCode> 开始,转换为 Stream<InviteCode>
    2. 对流中的每个 InviteCode 对象:
      • 使用 InviteCode::getId 提取键(id)。
      • 使用 ic -> ic 提取值(InviteCode 对象)。
    3. 将所有键值对收集到 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 > 0InviteCode
    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 源码。点赞和分享哦!😊

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2319641.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

当全球化成为商业常态,Shopify 如何为品牌生意铺平出海之路?

从独立站搭建到支付履约&#xff0c;从数据分析到生态整合&#xff0c;Shopify 为不同规模的企业提供可扩展的解决方案。 在数字化浪潮的推动下&#xff0c;跨境电商与品牌出海的黄金时代已然到来。然而&#xff0c;看似广阔的市场蓝海背后&#xff0c;是无数企业正在经历的“成…

RC6在线加密工具

RC6加密算法是一种基于RC5改进的分组密码算法&#xff0c;曾作为AES&#xff08;高级加密标准&#xff09;的候选算法之一。它采用了4个32位寄存器&#xff0c;增加了32位整数乘法运算&#xff0c;以增强扩散和混淆特性&#xff0c;提高了安全性。RC6的设计简单、高效&#xff…

python每日十题(5)

保留字&#xff0c;也称关键字&#xff0c;是指被编程语言内部定义并保留使用的标识符。Python 3.x版本中有35个保留字&#xff0c;分别为&#xff1a;and, as,assert,async,await,break,class,continue,def,del,elif,else, except, False, finally,for,from,global, if,import…

应用案例 | 核能工业:M-PM助力核工业科研项目

M-PM助力核工业科研项目 一、项目背景 在核工业复杂系统的研发进程中&#xff0c;MBSE&#xff08;基于模型的系统工程&#xff09;方法的应用愈发成熟&#xff0c;已然成为推动系统设计与优化的关键力量。如今&#xff0c;各相关设计系统的 MBSE 模型数据呈现出精细化、多元…

4.1、网络安全模型

目录 网络安全体系概述网络安全模型-BLP模型网络安全模型-Biba模型网络安全模型 - 信息流模型信息保障模型能力成熟度模型其它安全模型网络安全原则 网络安全体系概述 网络安全体系是网络安全保证系统的最高层概念抽象&#xff0c;是一个体系&#xff0c;体系一般是一个概念&a…

ManiWAV:通过野外的音频-视频数据学习机器人操作

24年6月来自斯坦福大学、哥伦比亚大学和 TRI 的论文“ManiWAV: Learning Robot Manipulation from In-the-Wild Audio-Visual Data”。 音频信号通过接触为机器人交互和物体属性提供丰富的信息。这些信息可以简化接触丰富的机器人操作技能学习&#xff0c;尤其是当视觉信息本身…

Floyd 算法——97. 小明逛公园

卡码网:97. 小明逛公园https://kamacoder.com/problempage.php?pid=1155 题目描述 小明喜欢去公园散步,公园内布置了许多的景点,相互之间通过小路连接,小明希望在观看景点的同时,能够节省体力,走最短的路径。 给定一个公园景点图,图中有 N 个景点(编号为 1 到 N),…

QT二 QT使用generate form 生成常用UI,各种UI控件

一 。没有使用general form 和 使用 general form 后&#xff0c;file层面和代码层面的不同比较 file层面的不同 代码层面的不同&#xff0c; 在 使用了general form之后&#xff0c;在主界面的构造方法中&#xff0c;使用ui->setupUi(this),就完成了所有UI的处理。 而之…

多条件排序(C# and Lua)

C# 升序排序 OrderBy 按升序对序列的元素进行排序 ThenBy 按升序对序列中的元素执行后续排序 降序排序 OrderByDescending 按降序对序列的元素排序 ThenByDescending 按降序对序列中的元素执行后续排序 public class Fruit {public int id;public string name;publi…

人工智能之数学基础:线性方程组求解的得力助手——增广矩阵

本文重点 增广矩阵是一个极具实用价值的工具,尤其在处理线性方程组时,它展现了卓越的功效。通过整合系数和常数项,增广矩阵简化了计算过程并提供了判断方程组解集的有效方法。 增广矩阵的起源与定义 增广矩阵的概念源于线性方程组求解的需求。在解决线性方程组时,我们常…

关于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&#xff08;JSON Web Token&#xff09; 3. 认证方式对比与总结3.1 认证方式对比3.2 如何选择合适的认证方式&#xf…

【Android Studio开发】生命周期、Activity和组件通信(上)

零、前期配置 1.【Android】模式 2.点击【运行】&#xff0c;弹出模拟器 右侧是模拟机&#xff0c;显示Hello World 3. 打开【activity_main.xml】文件&#xff0c;点击【Design】&#xff0c;然后点击【Component Tree】 在弹出的Component Tree中右键【main】,选择【Conver…

【ES】Elasticsearch学习

文章目录 简单的安装 简单的安装 参考&#xff1a;https://blog.csdn.net/smilehappiness/article/details/118466378 官网&#xff1a;https://www.elastic.co/guide/en/elasticsearch/reference/current/targz.html 下载&#xff1a;https://www.elastic.co/cn/downloads/e…

实验三 Python 数据可视化 Python 聚类-K-means(CQUPT)

一、实验目的 Python 数据可视化&#xff1a; 1、学习使用 jieba、wordcloud 等类库生成词云图。 2、学习使用 Matplotlib 库进行数据可视化。 Python 聚类-K-means&#xff1a; 1、理解聚类非监督学习方法的基本原理。 2、掌握 Python、numpy、pandas、sklearn 实现聚类…

【STM32】SPI通信协议W25Q64Flash存储器芯片(学习笔记)

通信接口部分有介绍SPI&#xff1a;【STM32】USART串口协议&串口外设-学习笔记-CSDN博客 SPI通信协议 SPI通信 SPI&#xff08;Serial Peripheral Interface&#xff09;是由Motorola公司开发的一种通用数据总线四根通信线&#xff1a;SCK&#xff08;Serial Clock&…

SpringBoot最佳实践之 - 使用AOP记录操作日志

1. 前言 本篇博客是个人在工作中遇到的需求。针对此需求&#xff0c;开发了具体的实现代码。并不是普适的记录操作日志的方式。以阅读本篇博客的朋友&#xff0c;可以参考此篇博客中记录日志的方式&#xff0c;可能会对你有些许帮助和启发。 2. 需求描述 有一个后台管理系统…

第六届机电一体化技术与智能制造国际学术会议(ICMTIM 2025)

重要信息 4月11-13日 南京江北新区工业大学亚朵酒店 www.icmtim.org&#xff08;点击了解参会投稿等&#xff09; 简介 由南京工业大学主办&#xff0c;南京工业大学电气工程与控制科学学院、中国矿业大学、黑龙江大学、江苏省自动化学会承办的第六届机电一体化技术…

期刊分区表2025年名单下载(经济学、管理学)

2025年期刊分区表包括SCIE、SSCI、A&HCI、ESCI和OAJ&#xff0c;共设置了包括自然科学、社会科学和人文科学在内的21个大类 本次分享的是期刊分区表2025年名单经济学类、管理学类&#xff0c;一共7631025条 一、数据介绍 数据名称&#xff1a;期刊分区表2025年名单 数据…

八股学习-JUC java并发编程

本文仅供个人学习使用&#xff0c;参考资料&#xff1a;JMM&#xff08;Java 内存模型&#xff09;详解 | JavaGuide 线程基础概念 用户线程&#xff1a;由用户空间程序管理和调度的线程&#xff0c;运行在用户空间。 内核线程&#xff1a;由操作系统内核管理和调度的线程&…