深入理解流(Streams)—— 聲明式數據處理的藝術

1. 引言

大家好!歡迎來到本系列博客的第三篇。在前兩篇文章中,我們已經領略了 Java 8 中 行為參數化Lambda 表達式 的魅力。

  • 在第 1 章 Java行為參數化:從啰嗦到簡潔的代碼進化中,我們了解到如何通過將行為(代碼塊)作為參數傳遞給方法,使代碼更靈活、可復用。
  • 在第 2 章 Java 8 Lambda表達式詳解:從入門到實踐中,我們深入學習了 Lambda 表達式,它是實現行為參數化的簡潔而強大的工具。

強烈建議先閱讀前兩篇文章,它們為理解今天的主題——Java 8 中的“流”(Streams)——奠定了基礎。

那么,什么是“流”?它為何如此重要?

簡而言之,Java 8 的“流”提供了一種全新的、聲明式的處理數據的方式。它允許你以類似于 SQL 查詢的風格操作集合(及其他數據源),無需編寫冗長的循環和條件語句。

想象一下工廠的流水線:原材料(數據)從一端進入,經過一系列處理工序(操作),最終產出成品。Java 8 中,“流”就像這條流水線,數據在其中流動,我們可以通過各種“流操作”對其進行 篩選轉換排序分組 等。

本篇我們將深入探討“流”的方方面面:

  • 流的定義
  • 流的特性
  • 流與集合的區別
  • 流的核心操作
  • 如何利用流編寫更簡潔、高效、易于理解的代碼

讓我們一起開啟 Java 8“流”的探索之旅!

2. 流是什么?(What are Streams?)

引言中,我們用流水線類比了“流”。現在,讓我們揭開“流”的神秘面紗。

流是“從支持數據處理操作的源生成的一系列元素”

——《Java 8 in Action》

讓我們拆解這個定義:

  • 一系列元素: 與集合類似,流也是一系列元素的集合。你可以把一堆蘋果放進籃子(集合),也可以把它們放在流水線(流)上。關鍵在于,流關注的是如何處理這些元素,而不是如何存儲它們。

  • 源: 流中的元素從哪里來?答案是“源”。它可以是:

    • 集合 (List, Set 等)
    • 數組
    • I/O 資源 (文件等)
    • 生成函數 (例如,產生無限序列的函數) 流本身不存儲數據,它只是從源頭獲取數據。
  • 數據處理操作: 這是流的核心!流提供了一套豐富的操作,讓你對數據進行各種處理,類似數據庫查詢操作:

    • filter: 篩選符合條件的元素。
    • map: 將元素轉換為另一種形式(如小寫字母轉大寫)。
    • reduce: 將所有元素組合成一個結果(如求和)。
    • sort: 排序。
    • … 還有很多!
  • 內部迭代: 通常,我們用 for 循環或 forEach 顯式遍歷集合(外部迭代)。而流則不同,它在內部迭代。你只需要告訴流_你想要做什么_,無需關心_如何做_。這使代碼更簡潔,也更容易優化(如并行處理)。

流不是新的數據結構,而是更高層次的抽象。它專注于 做什么(數據處理),而不是 怎么做(迭代細節)。流像管道,數據從源頭流入,經過一系列處理,產生結果。這種聲明式編程風格使代碼更易讀、維護。

3. 流與集合(Streams vs. Collections)

Java 8 的「流」常與集合(Collections)比較。雖都用于處理數據,但兩者差異顯著。理解這些差異對于有效使用流至關重要。

相同點:

  • 存儲元素: 流和集合都可存儲一系列元素。

不同點:

特性集合 (Collections)流 (Streams)
主要目的存儲和訪問元素對元素進行計算
何時計算元素在加入集合時就已計算好元素在需要時才計算(延遲計算/惰性求值
迭代方式外部迭代(用戶代碼控制迭代)內部迭代(流庫自身控制迭代)
遍歷次數可以多次遍歷只能遍歷一次
數據修改可以添加、刪除、修改集合中的元素流操作通常不修改數據源
數據結構是一種數據結構,主要目的是以特定的時間/空間復雜度存儲和訪問數據不是數據結構,它沒有存儲空間,主要目的是對數據源進行計算。

詳細解釋幾個關鍵區別:

3.1 只能遍歷一次

這是流的重要限制。一旦對流執行終端操作(如 forEachcollect),流就被“消費”,不能再用。再次遍歷會拋 IllegalStateException

代碼示例:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Stream<String> nameStream = names.stream();// 第一次遍歷:打印名字
nameStream.forEach(System.out::println);// 第二次遍歷:會拋出異常!
// nameStream.forEach(System.out::println); // java.lang.IllegalStateException: stream has already been operated upon or closed

這與集合形成對比,集合可多次遍歷。

3.2 外部迭代與內部迭代
  • 外部迭代(集合): 編寫顯式循環(如 for-each)遍歷集合,并處理元素。你完全掌控迭代過程。
  • 內部迭代(流): 只需告訴流你想做什么(如篩選長度大于3的名字),流內部進行迭代和處理。無需編寫循環,代碼更簡潔。

代碼示例:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");// 外部迭代(集合)
List<String> longNames1 = new ArrayList<>();
for (String name : names) {if (name.length() > 3) {longNames1.add(name);}
}
System.out.println(longNames1); // [Alice, Charlie, David]// 內部迭代(流)
List<String> longNames2 = names.stream().filter(name -> name.length() > 3).collect(Collectors.toList());
System.out.println(longNames2); // [Alice, Charlie, David]

流(內部迭代)代碼更簡潔、易讀,更接近聲明式編程。我們描述了想要什么(篩選長度大于3的名字),未指定如何做(循環和條件判斷)。

3.3 延遲計算/惰性求值

這是流的重要特性。流的中間操作(如filter,map)延遲計算。遇到終端操作前,中間操作不執行。終端操作觸發時,才計算。

4. 流操作詳解 (Stream Operations in Detail)

流的強大在于其豐富的操作,讓你以聲明式方式處理數據。操作分兩類:中間操作終端操作。理解這兩類操作及如何協同工作,是掌握流的關鍵。

4.1 中間操作 (Intermediate Operations)

特點:

  • 返回另一個流: 每個中間操作返回新流。可將多個中間操作鏈接,形成“流水線”。
  • 延遲執行(Lazy): 中間操作不立即執行,只構建流水線。終端操作觸發時,中間操作才執行。

常見中間操作:

操作描述示例
filter篩選符合條件的元素stream.filter(x -> x > 5)
map將每個元素映射為另一個元素(類型可能不同)stream.map(String::toUpperCase)
limit截取流的前 N 個元素stream.limit(10)
skip跳過流的前 N 個元素stream.skip(5)
distinct去除流中的重復元素(根據 equalsstream.distinct()
sorted對流中的元素排序(自然排序或根據 Comparatorstream.sorted() stream.sorted(Comparator.reverseOrder())
peek對流中每個元素執行一個操作,但不改變流內容(主要用于調試)stream.peek(System.out::println)
flatMap將每個元素轉換為一個流,然后將這些流合并為一個流。stream.flatMap(Collection::stream)

代碼示例 (中間操作鏈):

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");List<String> result = names.stream().filter(name -> name.length() > 3)  // 篩選長度大于3的名字.map(String::toLowerCase)          // 轉小寫.sorted()                          // 排序.collect(Collectors.toList());     // 收集結果System.out.println(result); // [alice, charlie, david]

filtermapsorted 是中間操作。它們鏈接成流水線。注意,直到 collect(終端操作)被調用,中間操作才執行。

4.2 終端操作 (Terminal Operations)

特點:

  • 產生結果或副作用: 終端操作觸發流水線執行,產生結果(非流值)或副作用(如打印)。
  • 消費流: 終端操作執行后,流被消費,不能再用。

常見終端操作:

操作描述示例
forEach對流中每個元素執行一個操作(副作用)stream.forEach(System.out::println)
count返回流中元素個數long count = stream.count()
collect將流中元素收集到集合(或其他數據結構)List<String> list = stream.collect(Collectors.toList())
reduce將流中元素組合成一個值(如求和、求最大值)Optional<Integer> sum = stream.reduce(Integer::sum)
anyMatch檢查是否至少有一個元素匹配給定條件boolean hasLongName = stream.anyMatch(s -> s.length() > 5)
allMatch檢查是否所有元素都匹配給定條件boolean allUpperCase = stream.allMatch(s -> Character.isUpperCase(s.charAt(0)))
noneMatch檢查是否沒有元素匹配給定條件boolean noEmptyString = stream.noneMatch(String::isEmpty)
findFirst返回流中第一個元素(Optional)Optional<String> first = stream.findFirst()
findAny返回流中任意一個元素(Optional,并行流中更常用)Optional<String> any = stream.findAny()

代碼示例 (終端操作):

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);// 求和
int sum = numbers.stream().reduce(0, Integer::sum); // 初始值為0,用 Integer.sum() 累加
System.out.println("Sum: " + sum); // Sum: 15// 查找第一個偶數
Optional<Integer> firstEven = numbers.stream().filter(n -> n % 2 == 0).findFirst();
firstEven.ifPresent(System.out::println); // 2 (若存在偶數)// 檢查是否所有數字都大于0
boolean allPositive = numbers.stream().allMatch(n -> n > 0);
System.out.println("All positive: " + allPositive); // All positive: true

5. 流的“按需計算”(On-Demand Computation)

前面多次提到流的“延遲計算”/“惰性求值”。現在深入探討。

5.1 什么是“按需計算”?

流中元素只在真正需要時才計算。與集合對比,集合中所有元素在創建時就已存在于內存。

5.2 為什么“按需計算”重要?

帶來幾個關鍵優勢:

  1. 效率提升: 若非所有元素都需處理,“按需計算”可避免不必要計算,提高效率。處理大數據集時,優勢明顯。

  2. 短路操作: “按需計算”使“短路操作”(如 findFirstanyMatch)成為可能。找到滿足條件的元素,就無需處理剩余元素。

  3. 無限流: “按需計算”使創建“無限流”(Infinite Streams)成為可能。無限流無固定結尾,可根據需要生成無限多元素。

5.3 “按需計算”如何工作?

通過中間操作和終端操作協同實現。

  • 中間操作:懶惰”。只構建處理流水線,不立即執行。
  • 終端操作:急切”。終端操作被調用,觸發流水線執行。

終端操作需要元素時,流水線上中間操作才處理數據源。中間操作通常非一次處理一個元素,而是按需逐個處理。

代碼示例(演示“按需計算”):

 List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);Optional<Integer> firstEvenGreaterThan5 = numbers.stream().filter(n -> {System.out.println("Filtering: " + n); // 打印過濾操作的中間結果return n % 2 == 0;}).filter(n -> {System.out.println("Filtering again: "+n);return n > 5;}).findFirst();firstEvenGreaterThan5.ifPresent(n -> System.out.println("Result: " + n));

輸出:

Filtering: 1
Filtering: 2
Filtering again: 2
Filtering: 3
Filtering: 4
Filtering again: 4
Filtering: 5
Filtering: 6
Filtering again: 6
Result: 6

分析:

從輸出可見:

  1. 并非所有數字都被 filter 處理。
  2. findFirst 找到第一個滿足條件的元素(6),后續元素不再處理。
  3. 兩個filter非獨立,而是交替執行。

這就是“按需計算”。流只處理必要元素,找到 findFirst 要求的結果。

6.總結

Java 8 的流(Streams)是一種強大而優雅的數據處理工具。它通過聲明式、函數式的風格,使代碼更簡潔、易讀、高效。

在這篇文章中,我們深入探討了:

  • 流的本質: 一種支持數據處理操作的元素序列,強調“做什么”而非“怎么做”。
  • 流與集合的區別: 延遲計算、內部迭代、一次性遍歷等。
  • 流的操作: 中間操作(構建流水線)和終端操作(觸發計算)。
  • 按需計算: 流的關鍵特性,提高效率、支持短路操作和無限流。

掌握了流,你就掌握了 Java 8 中最強大的武器之一。在后續的文章中,我們會進一步探索流的高級用法,包括并行流、自定義收集器等。敬請期待!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/894872.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/894872.shtml
英文地址,請注明出處:http://en.pswp.cn/news/894872.shtml

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

相關文章

【Linux】之【Get√】nmcli device wifi list 與 wpa_cli scan 和 wpa_cli scan_result 區別

nmcli device wifi list 是 NetworkManager 的命令行工具 nmcli 的一部分&#xff0c;它用于列出當前可用的無線網絡。它的作用和 wpa_cli 的掃描功能類似&#xff0c;但有一些不同點。 1. nmcli device wifi list 功能&#xff1a; nmcli device wifi list 命令用于顯示當前…

【藍橋杯嵌入式】6_定時器輸入捕獲

全部代碼網盤自取 鏈接&#xff1a;https://pan.baidu.com/s/1PX2NCQxnADxYBQx5CsOgPA?pwd3ii2 提取碼&#xff1a;3ii2 這是兩個信號發生器&#xff0c;可以通過調節板上的兩個電位器R39和R40調節輸出頻率。 將PB4、PA15選擇ch1&#xff0c;兩個信號發生器只能選擇TIM3和TIM…

詳解SQLAlchemy的函數relationship

在 SQLAlchemy 中&#xff0c;relationship 是一個非常重要的函數&#xff0c;用于定義模型之間的關系。它用于在 ORM 層面上表示數據庫表之間的關聯關系&#xff08;如 1 對 1、1 對多和多對多&#xff09;。relationship 的主要作用是提供一個高級接口&#xff0c;用于在模型…

分桶函數的使用

除了 NTILE 函數&#xff0c;SQL 中還有其他一些與 分桶&#xff08;bucketization&#xff09;相關的函數&#xff0c;雖然它們的實現方式不同&#xff0c;但都涉及將數據分成多個區間或組。以下是一些常用的分桶函數&#xff1a; 1. CASE 語句 雖然 CASE 不是開窗函數&…

iOS 音頻錄制、播放與格式轉換

iOS 音頻錄制、播放與格式轉換:基于 AVFoundation 和 FFmpegKit 的實現 在 iOS 開發中,音頻處理是一個非常常見的需求,比如錄音、播放音頻、音頻格式轉換等。本文將詳細解讀一段基于 AVFoundation 和 FFmpegKit 的代碼,展示如何實現音頻錄制、播放以及 PCM 和 AAC 格式之間…

數據結構與算法(test1)

一、樹和二叉樹 1. 看圖&#xff0c;完成以下填空 (1).樹的度為________。 (2).樹中結點的最大層次&#xff0c;稱為樹的_____或樹的______&#xff0c;值是______。 (3).結點A和B的度分別為________ 和 ________。 (4).結點A是結點B的________。 (5).結點B是結點A的________…

新版AndroidStudio 修改 jdk版本

一、問題 之前&#xff0c;在安卓項目中配置JDK和Gradle的過程非常直觀&#xff0c;只需要進入Android Studio的File菜單中的Project Structure即可進行設置&#xff0c;十分方便。 如下圖可以在這修改JDK: 但是升級AndroidStudio之后&#xff0c;比如我升級到了Android Stu…

litemall,又一個小商場系統

litemall Spring Boot后端 Vue管理員前端 微信小程序用戶前端 Vue用戶移動端 代碼地址&#xff1a;litemall: 又一個小商城。 litemall Spring Boot后端 Vue管理員前端 微信小程序用戶前端 Vue用戶移動端

cursor 開發java項目教程簡單上手

1.官網下載 Cursor - The AI Code Editor 下載完后注冊賬號&#xff0c;可以使用無限郵的方式 注冊完之后 設置中文 可以選擇設置為中文 Ctrl Shift X 進入設置頁面輸入chinese 然后重啟 更改jdk跟maven倉庫設置 ctrlshiftp 打開輸入框后輸入json&#xff0c;把下面代碼…

安裝和使用 Ollama(實驗環境windows)

下載安裝 下載 https://ollama.com/download/windows 安裝 Windows 安裝 如果直接雙擊 OllamaSetup.exe 安裝&#xff0c;默認會安裝到 C 盤&#xff0c;如果需要指定安裝目錄&#xff0c;需要通過命令行指定安裝地址&#xff0c;如下&#xff1a; # 切換到安裝目錄 C:\Use…

[原創](Modern C++)現代C++的關鍵性概念: 文件編碼細節之一:BOM(Byte Order Mark, 字節順序標記)

常用網名: 豬頭三 出生日期: 1981.XX.XX 企鵝交流: 643439947 個人網站: 80x86匯編小站 編程生涯: 2001年~至今[共24年] 職業生涯: 22年 開發語言: C/C、80x86ASM、PHP、Perl、Objective-C、Object Pascal、C#、Python 開發工具: Visual Studio、Delphi、XCode、Eclipse、C Bui…

LQB(0)-python-基礎知識

一、Python開發環境與基礎知識 python解釋器&#xff1a;用于解釋python代碼 方式&#xff1a; 1.直接安裝python解釋器 2.安裝Anaconda管理python環境 python開發環境&#xff1a;用于編寫python代碼 1.vscode 2.pycharm # 3.安裝Anaconda后可以使用網頁版的jupyter n…

C# 中記錄(Record)詳解

從C#9.0開始&#xff0c;我們有了一個有趣的語法糖&#xff1a;記錄(record)   為什么提供記錄&#xff1f; 開發過程中&#xff0c;我們往往會創建一些簡單的實體&#xff0c;它們僅僅擁有一些簡單的屬性&#xff0c;可能還有幾個簡單的方法&#xff0c;比如DTO等等&#xf…

使用 CSS 實現透明效果

在 CSS 中&#xff0c;實現透明效果有幾種方法&#xff0c;具體使用哪種方法取決于具體需求。以下是一些常見的方法&#xff1a; 使用 opacity 屬性&#xff1a; opacity 屬性可以設置整個元素的透明度&#xff0c;包括其所有的子元素。 .transparent { opacity: 0.5; /* 0 表…

MS17-010(永恒之藍1.0)漏洞遠程控制win7系統操作實戰小白通俗易懂

1.準備環境win7操作系統&#xff08;被攻擊機&#xff09;以及kali系統&#xff08;攻擊機&#xff09;&#xff0c;kali使用msf工具進行攻擊。 2.打開kali終端&#xff0c;進入msf&#xff0c;輸入msfconsole然后等待啟動。 ┌──(root?kali-chifan)-[~] └─# msfconsole…

C語言:函數棧幀的創建和銷毀

目錄 1.什么是函數棧幀2.理解函數棧幀能解決什么問題3.函數棧幀的創建和銷毀的過程解析3.1 什么是棧3.2 認識相關寄存器和匯編指令3.3 解析函數棧幀的創建和銷毀過程3.3.1 準備環境3.3.2 函數的調用堆棧3.3.3 轉到反匯編3.3.4 函數棧幀的創建和銷毀 1.什么是函數棧幀 在寫C語言…

25/2/6 <機器人基礎> 運動學中各連桿的變換矩陣求法

變換矩陣 機器人通常包含多個關節和連桿&#xff0c;每個關節和連桿都有自己的局部坐標系。變換矩陣能夠將一個點或向量從一個坐標系轉換到另一個坐標系&#xff0c;從而實現對機器人各個部件位置和姿態的統一描述 變換矩陣能夠將復雜的運動分解為旋轉和平移的組合。通過矩陣乘…

AllData數據中臺核心菜單十二:數據同步平臺

&#x1f525;&#x1f525; AllData大數據產品是可定義數據中臺&#xff0c;以數據平臺為底座&#xff0c;以數據中臺為橋梁&#xff0c;以機器學習平臺為中層框架&#xff0c;以大模型應用為上游產品&#xff0c;提供全鏈路數字化解決方案。 ?奧零數據科技官網&#xff1a;…

【FPGA】 MIPS 12條整數指令 【3】

實現乘除 修改框架 EX&#xff1a;實現帶符號乘除法和無符號乘除法 HiLo寄存器&#xff1a;用于存放乘法和除法的運算結果。Hi、Lo為32bit寄存器。電路描述與實現RegFile思想一致 仿真 代碼 DataMem.v include "define.v"; module DataMem(input wire clk,input…

【原子工具】快速冪 快速乘

題冪算.一切即1 陰陽迭變積微著&#xff0c;疊浪層巒瞬息功 莫道浮生千萬事&#xff0c;元知萬象一歸宗 文章目錄 快速冪原始快速冪&#xff08;O(logn)&#xff09;二分遞歸形式非遞歸形式 模下意義的快速冪&#xff08;O(logn)&#xff09;二分遞歸形式非遞歸形式 快速乘龜速…