主頁 > 後端開發 > Java8 新特性 —— Stream 流式編程

Java8 新特性 —— Stream 流式編程

2020-11-14 20:41:21 後端開發


本文部分摘自 On Java 8


流概述

集合優化了物件的存盤,大多數情況下,我們將物件存盤在集合是為了處理他們,使用流可以幫助我們處理物件,無需迭代集合中的元素,即可直接提取和操作元素,并添加了很多便利的操作,例如查找、過濾、分組、排序等一系列操作,

流的一個核心好處是:它使得程式更加短小并且易于理解,當結合 Lambda 運算式和方法參考時,會讓人感覺自成一體,總而言之,流就是一種高效且易于使用的處理資料的方式,

觀察下面的例子:

public class Randoms {
    
    public static void main(String[] args) {
        new Random(47)	// 創建 Random 物件,并給一個種子
            .ints(5, 20)	// 產生一個限定了邊界的隨機整數流
            .distinct()	// 使流中的整數不重復
            .limit(7)	// 取前7個元素
            .sorted()	// 排序
            .forEach(System.out::println);	// 根據傳遞給它的函式對流中每個物件執行操作
    }
}

通過上面的示例,我們可以發現流有如下特點:

  1. 流本身不存盤元素,并且不會改變源物件,相反,它會回傳一個持有結果的新流
  2. 流可以在不使用賦值或可變資料的情況下對有狀態的系統建模
  3. 流是一種宣告式編程風格,它宣告想要做什么,而非指明如何做
  4. 流的迭代過稱為內部迭代,你看不到迭代程序,可讀性更強
  5. 流是懶加載的,它會等到需要時才執行

流創建

創建流的方式有很多,下面逐個介紹:

1. Stream.of()

通過 Stream.of() 可以很容易地將一組元素轉化為流

Stream.of(new Bubble(1), new Bubble(2), new Bubble(3)).forEach(System.out::println);
Stream.of("a", "b", "c", "d", "e", "f").forEach(System.out::print);
Stream.of(3.14159, 2.718, 1.618).forEach(System.out::println);

2. stream()

每個集合也可以通過呼叫 stream() 方法來產生一個流

List<Bubble> list = Arrays.asList(new Bubble(1), new Bubble(2), new Bubble(3));
list.stream().forEach(System.out::print);
Set<String> set = new HashSet<>(Arrays.asList("a", "b", "c", "d", "e", "f"));
set.stream().forEach(System.out::print);

3. Stream.generate()

使用 Stream.generate() 搭配 Supplier<T> 生成 T 型別的流

Stream.generate(Math::random).limit(10).forEach(System.out::print);

4. Stream.iterate()

Stream.iterate() 產生的流的第一個元素是種子,然后把種子傳遞給方法,方法的運行結果被添加到流,并作為下次呼叫 iterate() 的第一個引數

Stream.iterate(0, n -> n + 1).limit(10).forEach(System.out::print)

使用 Stream.generate()Stream.iterate() 生成的無限流一定要用 limit() 截斷

5. Stream.builder()

使用建造者模式創建一個 builder 物件,然后將創建流所需的多個資訊傳遞給它,最后 builder 物件執行創建流的操作

Stream.Builder<String> builder = Stream.builder();
builder.add("a");
builder.add("b");
...
builder.build();	// 創建流
// builder.add("c")	// 呼叫 build() 方法后繼續添加元素會產生例外

6. Arrays.stream()

Arrays 類中有一個名為 stream() 的靜態方法用于把陣列轉換成流

Arrays.stream(new double[] {3.14159, 2.718, 1.618}).forEach(System.out::print);
Arrays.stream(new int[] {1, 3, 5}).forEach(System.out::print);
Arrays.stream(new long[] {11, 22, 44, 66}).forEach(System.out::print);
// 選擇一個子域
Arrays.stream(new int[] {1, 3, 5, 7, 15, 28, 37}, 3, 6).forEach(System.out::print);

最后一次 stream() 的呼叫有兩個額外的引數,第一個引數告訴 stream() 從陣列的哪個位置開始選擇元素,第二個引數告知在哪里停止

7. IntStream.range()

IntStream 類提供 range() 方法用于生成整型序列的流,撰寫回圈時,這個方法會更加便利

IntStream.range(10, 20).sum();	// 求得 10 - 20 的序列和
IntStream.range(10, 20).forEach(System.out::print);	// 回圈輸出 10 - 20

8. 亂數流

Random 類被一組生成流的方式增強了,可以生成一組亂數流

Random rand = new Random(47);
// 產生一個隨機流
rand.ints().boxed();
// 控制上限和下限
rand.ints(10, 20).boxed();
// 控制流的大小
rand.ints(2).boxed();
// 控制流的大小和界限
rand.ints(3, 3, 9).boxed();

Random 類除了能生成基本型別 int,long,double 的流,使用 boxed() 操作會自動把基本型別包裝為對應的裝箱型別

9. 正則運算式

Java8 在 java.util.regex.Pattern 中新增了一個方法 splitAsStream(),這個方法可以根據傳入的公式將字符序列轉化為流

Pattern.compile("[.,?]+").splitAsStream("a,b,c,d,e").forEach(System.out::print);

中間操作

中間操作具體包括去重、過濾、映射等操作,作用于從流中獲取的每一個物件,并回傳一個新的流物件,

1. 跟蹤和除錯

peek() 操作的目的是幫助除錯,它允許你無修改地查看流中的元素

Stream.of("a b c d e".split(" ")).map(w -> w + " ").peek(System.out::print);

2. 流元素排序

sorted() 可以幫助我們實作對流元素的排序,如果不使用默認的自然排序,則需要傳入一個比較器,也可以把 Lambda 函式作為引數傳遞給 sorted()

Stream.of("a b c d e".split(" ")).sorted(Comparator.reverseOrder())
    .map(w -> w + " ").peek(System.out::print);

3. 移除元素

distinct() 可用于消除流中的重復元素

new Random(47).ints(5, 20).distinct().limit(7).forEach(System.out::println);

filter(Predicate) 將元素傳遞給過濾函式,若結果為 true,則保留元素

// 檢測質數
Stream.iterate(2, n -> n + 1).filter(i -> i % 2 ==0)
    .limit(10).forEach(System.out::print)

4. 應用函式到元素

map(Function) 將函式操作應用到輸入流的元素,并將回傳值傳遞到輸出流

Arrays.stream(new String[] {"12", "23", "34"}).map(s -> "[" + s + "]")
    .forEach(System.out::print)

另外還有 mapToInt(ToIntFunction)mapToLong(ToLongFunction)mapToDouble(ToDoubleFunction),操作和 map(Function) 相似,只是結果流為各自對應的基本型別

如果在將函式應用到元素的程序中拋出了例外,此時會把原始元素放到輸出流

5. 組合流

使用 flatMap() 將產生流的函式應用在每個元素上,然后將產生每個流都扁平化為元素

Stream.of(1, 2, 3).flatMap(i -> Stream.of("hello" + i)).forEach(System.out::println);

另外還有 flatMapToInt(Function)flatMapToLong(Function)flatMapToDouble(Function),操作和 flatMap() 相似,只是結果元素為各自對應的基本型別


Optional 類

如果在一個空流中嘗試獲取元素,結果肯定是得到一個例外,我們希望可以得到友好的提示,而不是糊你一臉 NullPointException,Optional 的出現就是為了解決臭名昭著的空指標例外

一些標準流操作回傳 Optional 物件,因為它們不能保證預期結果一定存在,包括:

  • findFirst()

    回傳一個包含第一個元素的 Optional 物件,如果流為空則回傳 Optional.empty

  • findAny()

    回傳包含任意元素的 Optional 物件,如果流為空則回傳 Optional.empty

  • max()min()

    回傳一個包含最大值或者最小值的 Optional 物件,如果流為空則回傳 Optional.empty

  • reduce(Function)

    將函式的回傳值包裝在 Optional 中

1. 便利函式

Optional 類本質上是一個容器物件,所謂容器是指:它可以保存型別 T 的值,也可以保存一個 null,此外,Optional 提供了許多有用的方法,可以幫助我們不用顯示地進行空值檢測:

  • ifPresent()

    是否有值存在,存在放回 true,否則回傳 false

  • ifPresent(Consumer)

    當值存在時呼叫 Consumer,否則什么也不做

  • orElse(otherObject)

    如果值存在則直接回傳,否則生成 otherObject

  • orElseGet(Supplier)

    如果值存在則直接回傳,否則使用 Supplier 函式生成一個可替代物件

  • orElseThrow(Supplier)

    如果值存在則直接回傳,否則使用 Supplier 函式生成一個例外

下面是對 Optional 的一個簡單應用

class OptionalBasics {
    
    static void test(Optional<String> optString) {
        if(optString.isPresent())
            System.out.println(optString.get()); 
        else
            System.out.println("Nothing inside!");
    }
    
    public static void main(String[] args) {
        test(Stream.of("Epithets").findFirst());
        test(Stream.<String>empty().findFirst());	// 生成一個空流
    }
}

2.創建 Optional

當我們需要在自己的代碼中加入 Optional 時,可以使用下面三個靜態方法:

  • empty()

    生成一個空 Optional

  • of(value)

    將一個非空值包裝到 Optional 里

  • ofNullable(value)

    針對一個可能為空的值,為空時自動生成 Optional.empty,否則將值包裝在 Optional 中

3. Optional 物件操作

當我們的流管道生成 Optional 物件,下面三個方法可以使得 Optional 能做更多后續操作:

  • filter(Predicate)

    對 Optional 中的內容應用 Predicate 并將結果回傳,如果 Optional 不滿足 Predicate,將 Optional 轉化為空 Optional ,如果 Optional 已經為空,則直接回傳空 Optional

  • map(Function)

    如果 Optional 不為空,應用 Function 于 Optional 中的內容,并回傳結果,否則直接回傳 Optional.empty

  • flatMap(Function)

    一般應用于已生成 Optional 的映射函式,所以 flatMap() 不會像 map() 那樣將結果封裝在 Optional 中


終端操作

終端操作將獲取流的最終結果,至此我們無法再繼續往后傳遞流,可以說,終端操作總是我們在使用流時所做的最后一件事

1. 陣列

當我們需要得到陣列型別的資料以便于后續操作時,可以使用下述方法產生陣列:

  • toArray()

    將流轉換成適當型別的陣列

  • toArray(generetor)

    生成自定義型別的陣列

2. 回圈

常見的如 forEach(Consumer),另外還有 forEachOrdered(Consumer),保證按照原始流的順序操作,第二種形式僅在引入并行流時才有意義,所謂并行流是將流分割為多個,并在不同的處理器上分別執行,由于多處理器并行操作的原因,輸出的結果可能會不一樣,因此需要用到 forEachOrdered(Consumer)

3. 集合

在這里我們只是簡單介紹一下常見的 Collectors 示例,實際上它還有一些非常復雜的實作,大多數情況下,java.util.stream.Collectors 中預設的 Collector 就能滿足我們的需求

  • collect(Collector)

    使用 Collector 收集流元素到結果集合中

  • collect(Supplier, BiConsumer, BiConsumer)

    第一個引數創建一個新的結果集合,第二個引數將下一個元素收集到結果集合中,第三個引數用于將兩個結果集合合并起來

4. 組合

組合意味著將流中所有元素以某種方式組合為一個元素

  • reduce(BinaryOperator)

    使用 BinaryOperator 來組合所有流中的元素,因為流可能為空,其回傳值為 Optional

  • reduce(identity, BinaryOperator)

    功能同上,但是使用 identity 作為其組合的初始值,因此如果流為空,identity 就是結果

看一段代碼示例:

Stream.generate(Math::random).limit(10)
	.reduce((fr0, fr1) -> fr0.size < 50 ? fr0 : fr1).ifPresent(System.out::println);

回傳的結果是 Optional 型別,Lambda 運算式中的第一個引數 fr0 是 reduce 中上一次呼叫的結果,而第二個引數 fr1 是從流傳遞過來的值

5. 匹配

allMatch(Predicate)

如果流的每個元素提供給 Predicate 都回傳 true ,結果回傳為 true,在第一個 false 時,則停止執行計算

anyMatch(Predicate)

如果流的任意一個元素提供給 Predicate 回傳 true ,結果回傳為 true,在第一個 true 是停止執行計算

noneMatch(Predicate)

如果流的每個元素提供給 Predicate 都回傳 false 時,結果回傳為 true,在第一個 true 時停止執行計算

6. 查找

findFirst()

回傳第一個流元素的 Optional,如果流為慷訓傳 Optional.empty

findAny(

回傳含有任意流元素的 Optional,如果流為慷訓傳 Optional.empty

7. 資訊

count()

流中的元素個數

max(Comparator)

根據所傳入的 Comparator 所決定的最大元素

min(Comparator)

根據所傳入的 Comparator 所決定的最小元素

8. 數字流資訊

average()

求取流元素平均值

max()min()

數值流操作無需 Comparator

sum()

對所有流元素進行求和


轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/215865.html

標籤:Java

上一篇:Spring Cloud 整合分布式鏈路追蹤系統Sleuth和ZipKin實戰,分析系統瓶頸

下一篇:元類

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more