深入解析 Java Stream API:從 List 到 Map 的優雅轉換!!!

🚀 深入解析 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 Setterspublic 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.pswp.cn/news/898630.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/898630.shtml
英文地址,請注明出處:http://en.pswp.cn/news/898630.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

配置銀河麒麟V10高級服務器操作系統安裝vmware tools。在您的計算機上尚未找到用于此虛擬機的 VMwareTools。安裝將無法繼續。

配置銀河麒麟V10高級服務器操作系統安裝vmware tools 下載VMwareTools安裝包 通過網盤分享的文件&#xff1a;VMwareTools-10.3.25-20206839.tar.gz 鏈接: https://pan.baidu.com/s/1EgMcqbIEur4iyHu2l0v_gQ?pwdrc8m 提取碼: rc8m 通過工具上傳到指定目錄&#xff0c;然后切換…

突破 HTML 學習瓶頸:表格、列表與表單的學習進度(一)

HTML 學習之困&#xff0c;如何破局&#xff1f; 作為一名熱衷于網頁開發的博主&#xff0c;在 HTML 的學習道路上&#xff0c;我可謂是 “過關斬將”&#xff0c;但也遇到過不少 “硬茬”。起初&#xff0c;當我滿心歡喜地以為掌握了基本的 HTML 標簽&#xff0c;就能輕松搭建…

理一理Mysql日期

在 MySQL 數據庫中&#xff0c;關于日期和時間的類型主要有以下幾種&#xff1a; 1. **DATE**: 僅存儲日期部分&#xff0c;格式為 YYYY-MM-DD&#xff0c;例如 2023-10-31。 2. **TIME**: 僅存儲時間部分&#xff0c;格式為 HH:MM:SS&#xff0c;例如 14:30:00。 3. **DATE…

CEF 多進程模式時,注入函數,獲得交互信息

CEF 控制臺添加一函數,枚舉 注冊的供前端使用的CPP交互函數有哪些-CSDN博客 上篇文章,是在模擬環境,單進程中設置的,這篇文章,將其改到正常多進程環境中設置。 對應于工程中的 CEF_RENDER項目 一、多進程模式中,改寫 修改步驟 1、注入函數 client_app_render.cpp 在…

C++ 介紹STL底層一些數據結構

c 標準模板庫中&#xff0c;set和map的底層實現通常基于紅黑樹&#xff0c;然們都是平衡二叉搜索樹(Balanceed Binary Serach Tree&#xff09;的一種,這種結構保證了 插入&#xff0c;刪除&#xff0c;查找的時間復雜度為O(log n)比普通二叉搜索樹更高效。 set set<T>…

在 Kubernetes(k8s)部署過程中常見的問題

在 Kubernetes(k8s)部署過程中,常見的問題主要包括以下幾類,以下是具體示例及簡要說明: 1. 資源配額不足(Resource Quota) 現象:Pod 處于 Pending 狀態,事件日志顯示 Insufficient CPU/Memory。 原因: 節點(Node)資源不足,無法滿足 Pod 的 requests 或 limits。 命…

Android Window浮窗UI組件使用JetPack

目前接手的一個業務&#xff0c;應用不是用Activity/Fragment作為界面組件&#xff0c;而是用Window浮窗的形式顯示&#xff0c;并且浮窗有很多種類型&#xff0c;每一種類型對應一類業務。那么怎么使用Jatpack的相關特性來設計架構并提高開發效率呢&#xff1f;分下面幾個模塊…

基于WebRtc,GB28181,Rtsp/Rtmp,SIP,JT1078,H265/WEB融合視頻會議接入方案

智能融合視頻會議系統方案—多協議、多場景、全兼容的一站式視頻協作平臺 OvMeet,LiveMeet針對用戶?核心痛點實現功能與用戶價值 &#xff0c;Web平臺實現MCU多協議&#xff0c;H265/H264等不同編碼監控&#xff0c;直播&#xff0c;會議&#xff0c;調度資源統一融合在一套界…

深入淺出理解LLM PPO:基于verl框架的實現解析之一

1. 寫在前面 強化學習(Reinforcement Learning,RL)在大型語言模型(Large Language Model,LLM)的訓練中扮演著越來越重要的角色。特別是近端策略優化(Proximal Policy Optimization,PPO)算法,已成為對齊LLM與人類偏好的主流方法之一。本文將基于verl框架(很多復刻De…

卷積神經網絡 - 匯聚層

卷積神經網絡一般由卷積層、匯聚層和全連接層構成&#xff0c;本文我們來學習匯聚層。 匯聚層(Pooling Layer)也叫子采樣層(Subsampling Layer)&#xff0c;其作用是進 行特征選擇&#xff0c;降低特征數量&#xff0c;從而減少參數數量。 卷積層雖然可以顯著減少網絡中連接的…

vue使用element-ui自定義樣式思路分享【實操】

前言 在使用第三方組件時&#xff0c;有時候組件提供的默認樣式不滿足我們的實際需求&#xff0c;需要對默認樣式進行調整&#xff0c;這就需要用到樣式穿透。本篇文章以vue3使用element-ui的Tabs組件&#xff0c;對Tabs組件的添加按鈕樣式進行客制化為例。 確定需要修改的組…

【工具分享】vscode+deepseek的接入與使用

目錄 第一章 前言 第二章 獲取Deepseek APIKEY 2.1 登錄與充值 2.2 創建API key 第三章 vscode接入deepseek并使用 3.1 vscode接入deepseek 3.2 vscode使用deepseek 第一章 前言 deepseek剛出來時有一段時間余額無法充值&#xff0c;導致小編沒法給大家發完整的流程&…

【藍橋杯速成】| 9.回溯升級

題目一&#xff1a;組合綜合 問題描述 39. 組合總和 - 力扣&#xff08;LeetCode&#xff09; 給你一個 無重復元素 的整數數組 candidates 和一個目標整數 target &#xff0c;找出 candidates 中可以使數字和為目標數 target 的 所有 不同組合 &#xff0c;并以列表形式返…

【C++進階】深入探索類型轉換

目錄 一、C語言中的類型轉換 1.1 隱式類型轉換 1.2. 顯式類型轉換 1.3.C語言類型轉換的局限性 二、C 類型轉換四劍客 2.1 static_cast&#xff1a;靜態類型轉換&#xff08;編譯期檢查&#xff09; 2.2 dynamic_cast&#xff1a;動態類型轉換&#xff08;運行時檢查&…

代碼隨想錄_動態規劃

代碼隨想錄 動態規劃 509.斐波那契數 509. 斐波那契數 斐波那契數 &#xff08;通常用 F(n) 表示&#xff09;形成的序列稱為 斐波那契數列 。該數列由 0 和 1 開始&#xff0c;后面的每一項數字都是前面兩項數字的和。也就是&#xff1a; F(0) 0&#xff0c;F(1) 1 F(n…

計算機基礎:編碼03,根據十進制數,求其原碼

專欄導航 本節文章分別屬于《Win32 學習筆記》和《MFC 學習筆記》兩個專欄&#xff0c;故劃分為兩個專欄導航。讀者可以自行選擇前往哪個專欄。 &#xff08;一&#xff09;WIn32 專欄導航 上一篇&#xff1a;計算機基礎&#xff1a;編碼02&#xff0c;有符號數編碼&#xf…

設計模式(創建型)-單例模式

摘要 在軟件開發的世界里&#xff0c;設計模式是開發者們智慧的結晶&#xff0c;它們為解決常見問題提供了經過驗證的通用方案。單例模式作為一種基礎且常用的設計模式&#xff0c;在許多場景中發揮著關鍵作用。本文將深入探討單例模式的定義、實現方式、應用場景以及可…

基于FPGA頻率、幅度、相位可調的任意函數發生器(DDS)實現

基于FPGA實現頻率、幅度、相位可調的DDS 1 摘要 直接數字合成器( DDS ) 是一種通過生成數字形式的時變信號并進行數模轉換來產生模擬波形(通常為正弦波)的方法,它通過數字方式直接合成信號,而不是通過模擬信號生成技術。DDS主要被應用于信號生成、通信系統中的本振、函…

本地JAR批量傳私服

在有網絡隔離的環境下&#xff0c;Maven項目如果沒有搭建私服就得把用到的通用組件通過U盤在每個組員間拷貝來拷貝去。非常的麻煩跟低效。搭建私服&#xff0c;如果通用組件很多的時候手工一個一個上傳更是非常的麻煩跟低效&#xff1b; 我就遇上這問題&#xff0c;跟A公司合作…

【ROS實戰】02-ROS架構介紹

1. 簡介 你是否曾有過這樣的疑問&#xff1a;我按照文檔安裝了ROS&#xff0c;依照要求寫了一些示例節點&#xff08;node&#xff09;、消息&#xff08;msg&#xff09;和話題&#xff08;topic&#xff09;&#xff0c;但覺得過程既麻煩又繁瑣。也許你開始懷疑&#xff1a;…