Java8 函數式編程stream流

来源:https://www.cnblogs.com/yuyiming/archive/2023/07/27/17586390.html
-Advertisement-
Play Games

## 開篇介紹 Java 8 中新增的特性旨在幫助程式員寫出更好的代碼,其中對核心類庫的改進是很關鍵的一部分,也是本章的主要內容。對核心類庫的改進主要包括集合類的 API 和新引入的流(Stream),流使程式員得以站在更高的抽象層次上對集合進行操作。下麵將介紹stream流的用法。 ## 1.初始 ...


開篇介紹

Java 8 中新增的特性旨在幫助程式員寫出更好的代碼,其中對核心類庫的改進是很關鍵的一部分,也是本章的主要內容。對核心類庫的改進主要包括集合類的 API 和新引入的流(Stream),流使程式員得以站在更高的抽象層次上對集合進行操作。下麵將介紹stream流的用法。

1.初始環境準備

​ 場景:現在有一個公司,公司部門有一級部門,二級部門甲和二級部門乙(其中二級部門甲和二級部門乙是一級部門的子部門),

一級部門下麵有有001號員工小明,二級部門甲下麵有002號員工小剛和003號員工小李,二級部門乙有002號員工小剛和004號員工小張,其中員工id是唯一的,員工小剛既是二級部門甲又是二級部門乙的員工。代碼展示如下:

public class LambdaUseCase {
    static List<Department> departmentList;
    static {
        // 一級部門,部門人員有001號員工小明
        Department departmentOne = new Department("一級部門", 1,10000L,11000L,
                Arrays.asList(new Person("001","小明",22)));
        // 二級部門甲,部門人員有002號員工小剛和003號員工小李
        Department departmentTwoFirst = new Department("二級部門甲", 2,8000L,13000L,
                Arrays.asList(new Person("002","小剛",23),
                        new Person("003","小李",32)));
        // 二級部門乙,部門人員有002號員工小剛和004號員工小張
        Department departmentTwoSecond = new Department("二級部門已", 2,7500L,15000L,
                Arrays.asList(new Person("002","小剛",23),
                        new Person("004","小張",34)));

        departmentList = Arrays.asList(departmentOne,departmentTwoFirst,departmentTwoSecond);
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class Department {
    // 部門名
    private String departmentName;
    // 部門等級
    private Integer departmenRank;
    // 部門薪資(單位分)
    private Long departSalary;
    // 部門日盈利
    private Long departProfit;
    // 部門人員集合
    private List<Person> persons;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
//重寫equal和hashcode方法,用於數據去重
@EqualsAndHashCode
class Person {
    // 人員id
    private String personId;
    // 人員姓名
    private String personName;
    // 人員年齡
    private Integer personAge;
}

2.創建流

單列集合: 集合對象.stream() 創建流

departmentList.stream();

數組:Arrays.stream(數組) 或者使用Stream.of來創建

Integer[] arr = {1,2,3,4,5};
Stream<Integer> stream = Arrays.stream(arr);
Stream<Integer> stream2 = Stream.of(arr);
Stream.of(1,2,3,4);

雙列集合:轉換成單列集合後再創建

Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
Stream<Map.Entry<String, Integer>> stream = map.entrySet().stream();

3.中間操作

3.1 map

如果有一個函數可以將一種類型的值轉換成另外一種類型,map 操作就可以使用該函數,將一個流中的值轉換成一個新的流

需求:公司今年收益提供,決定把所有部門的平均薪資提升1000。

List<Department> departments = departmentList.stream().map(e -> {
    e.setDepartSalary(e.getDepartSalary() + 1000);
    return e;
}).collect(Collectors.toList());

3.2 filter

對流中的元素進行過濾,篩選出符合過濾條件的數據

需求:需要篩選出部門平均薪資大於等於8000的數據。

List<Department> departments = departmentList.stream()
		.filter(e -> e.getDepartSalary() >= 8000)
		.collect(Collectors.toList());

3.3 flatMap

flatMap 方法可用 Stream 替換值,然後將多個 Stream 連接成一個 Stream

需求:把公司所有的人員收集到一個集合中。

不使用flatMap,你可能會這麼做,迴圈里套迴圈,看上去不太美觀。

Set<Person> personSet = new HashSet<>();
departmentList.stream().forEach(e->{
	e.getPersons().stream().forEach(person->{
		personSet.add(person);
	});
});

使用flatMap寫法。

Set<Person> personSet = departmentList.stream()
        .flatMap(e -> e.getPersons().stream())
        .collect(Collectors.toSet());

3.4 distinct

去除流中的重覆元素

需求:將公司所有的人員統計出來,這回不使用Set去重,註意:002號員工即在二級部門甲又在二級部門乙

List<Person> personSet = departmentList.stream()
        .flatMap(e -> e.getPersons().stream())
        .distinct()
        .collect(Collectors.toList());

註意:distinct方法是依賴Object的equals方法來判斷是否是相同對象的。所以需要註意重寫equals方法。

3.5 sorted

對流中的元素進行排序

需求: 按薪資從低到高將部門列出來。

List<Department> departments = departmentList.stream()
        .sorted((o1, o2) -> o1.getDepartSalary().compareTo(o2.getDepartSalary()))
        .collect(Collectors.toList());

註意:如果調用空參的sorted()方法,需要流中的元素是實現了Comparable。

3.6 limit

可以設置流的最大長度,超出的部分將被拋棄

需求:按薪資從低到高將部門列出來,並且找出薪資最低的倆個部門

List<Department> departments = departmentList.stream()
		.sorted(Comparator.comparing(Department::getDepartSalary))
		.limit(2)
		.collect(Collectors.toList());

3.7 skip

跳過流中的前n個元素,返回剩下的元素

需求:按薪資從低到高將部門列出來,並且忽略薪資最低的部門

List<Department> departments = departmentList.stream()
		.sorted(Comparator.comparing(Department::getDepartSalary))
		.skip(1)
		.collect(Collectors.toList());

4.終結操作

4.1 foreach

對流中的元素進行遍歷操作

需求:要求輸出全部部門的名稱

departmentList.stream()
                .forEach(e -> System.out.println(e.getDepartmentName()));

4.2 count

可以用來獲取當前流中元素的個數。

需求:統計部門的格式

long count = departmentList.stream()
    .count();

4.3 max和min

Stream 上常用的操作之一是求最大值和最小值

需求:分別找出部門裡面薪資最高的部門和最低的部門

Department maxDepartment = departmentList.stream()
		.max(Comparator.comparing(Department::getDepartSalary))
		.get();
Department minDepartment = departmentList.stream()
		.min(Comparator.comparing(Department::getDepartSalary))
		.get();

4.4 collect

將當前的流轉換為一個集合

4.4.1 Collectors.toList()

需求:獲得一個保存所有部門名字的集合

List<String> departNameList = departmentList.stream()
		.map(Department::getDepartmentName)
		.collect(Collectors.toList());

4.4.2 Collectors.toSet()

需求:獲得部門所有人的姓名(重名的忽略)

Set<String> personNameList = departmentList.stream()
		.flatMap(e -> e.getPersons().stream())
		.map(Person::getPersonName)
        .collect(Collectors.toSet());

4.4.3 Collectors.toMap(keyMapper, valueMapper)

需求:獲得部門名稱當做key,員工當做value的Map

Map<String, List<Person>> collect = departmentList.stream()
		.collect(Collectors.toMap(Department::getDepartmentName, Department::getPersons));

4.4.4 Collectors.joining()

需求:將所有的部門名字連起來,並且首碼是【,尾碼是】,分隔符是,

departmentList.stream()
		.map(Department::getDepartmentName)
		.collect(Collectors.joining(",","[","]"));

4.4.5 Collectors.partitioningBy(predicate)

接受一個流,並將其分成兩部分,使用 Predicate 對象判斷一個元素應該屬於哪個部分,並根據布爾值返回一個 Map 到列表。

需求:將一級部門和其他級別的部門分離出來

Map<Boolean, List<Department>> collect = departmentList
    .stream()
    .collect(Collectors.partitioningBy(e -> e.getDepartmenRank() == 1));

4.4.6 Collectors.groupingBy(classifier)

接受一個分類函數,用來對數據分組

需求:將部門按部門的階級分組

Map<Integer, List<Department>> collect = departmentList
    .stream()
    .collect(Collectors.groupingBy(Department::getDepartmenRank));

4.4.7 組合收集器

各種收集器已經很強大了,但如果將它們組合起來,會變得更強大。

需求:統計各個階級部門的平均薪資

Map<Integer, Double> collect = departmentList.stream()
.collect(Collectors.groupingBy(Department::getDepartmenRank,Collectors.averagingLong(Department::getDepartSalary)));

這個例子中用到了第二個收集器,用以收集最終結果的一個子集。這些收集器叫作下游收集器。收集器是生成最終結果的一劑配方,下游收集器則是生成部分結果的配方,主收集器中會用到下游收集器。這種組合使用收集器的方式,使得它們在 Stream 類庫中的作用更加強大。

4.5 reduce

對流中的數據按照指定的方式計算出結果

需求:計算出部門總的日盈利

Long sum = departmentList.stream()
		.map(Department::getDepartProfit)
		.reduce(0L, (result, element) -> result + element);

4.6 anyMatch

可以用來判斷是否有任意符合匹配條件的元素,結果為boolean類型。

需求:判斷部門中有沒有薪資大於9000的部門

boolean b = departmentList.stream()
                .anyMatch(e -> e.getDepartSalary() > 9000L);

4.7 allMatch

可以用來判斷是否都符合匹配條件,結果為boolean類型。如果都符合結果為true,否則結果為false。

需求:判斷部門薪資是不是都大於9000

boolean b = departmentList.stream()
                .allMatch(e -> e.getDepartSalary() > 9000L);

4.8 noneMatch

可以判斷流中的元素是否都不符合匹配條件。如果都不符合結果為true,否則結果為false

需求:判斷是不是所有的部門薪資都大於9000

boolean b = departmentList.stream()
                .noneMatch(e -> e.getDepartSalary() > 9000L);

4.9 findAny

獲取流中的任意一個元素。該方法沒有辦法保證獲取的一定是流中的第一個元素。

需求:找到任意一個部門

Department department = departmentList
                .stream()
                .findAny()
                .get();

4.10 findFirst

獲取流中的第一個元素。

需求:獲得薪資最低的一個部門

Department department = departmentList
                .stream()
                .sorted(Comparator.comparing(Department::getDepartSalary))
                .findFirst()
                .get();

5.高級用法

5.1 對基本類型處理(mapToInt,mapToLong,mapToDouble)

在 Java 中,有一些相伴的類型,比如 int 和 Integer——前者是基本類型,後者是裝箱類型。基本類型內建在語言和運行環境中,是基本的程式構建模塊;而裝箱類型屬於普通的 Java 類,只不過是對基本類型的一種封裝。由於裝箱類型是對象,因此在記憶體中存在額外開銷。比如,整型在記憶體中占用4 位元組,整型對象卻要占用 16 位元組。這一情況在數組上更加嚴重,整型數組中的每個元素只占用基本類型的記憶體,而整型對象數組中,每個元素都是記憶體中的一個指針,指向 Java堆中的某個對象。在最壞的情況下,同樣大小的數組,Integer[] 要比 int[] 多占用 6 倍記憶體。

將基本類型轉換為裝箱類型,稱為裝箱,反之則稱為拆箱,兩者都需要額外的計算開銷。對於需要大量數值運算的演算法來說,裝箱和拆箱的計算開銷,以及裝箱類型占用的額外記憶體,會明顯減緩程式的運行速度。

為了減小這些性能開銷,Stream 類的某些方法對基本類型和裝箱類型做了區分。高階函數 mapToLong 和其他類似函數即為該方面的一個嘗試。在 Java 8 中,僅對整型、長整型和雙浮點型做了特殊處理,因為它們在數值計算中用得最多,特殊處理後的系統性能提升效果最明顯。

如有可能,應儘可能多地使用對基本類型做過特殊處理的方法,進而改善性能。這些特殊的 Stream 還提供額外的方法,避免重覆實現一些通用的方法,讓代碼更能體現出數值計算的意圖。

需求:需要計算出公司部門利潤的平均值,最大值,最小值,以及利潤總和。

LongSummaryStatistics longSummaryStatistics = departmentList
    .stream()
    .mapToLong(Department::getDepartProfit)
    .summaryStatistics();
System.out.println("利潤最大值"+longSummaryStatistics.getMax());
System.out.println("利潤最小值"+longSummaryStatistics.getMin());
System.out.println("利潤平均值"+longSummaryStatistics.getAverage());
System.out.println("利潤總和"+longSummaryStatistics.getSum());

這些統計值在所有特殊處理的 Stream,如 DoubleStream、LongStream 中都可以得出。如無需全部的統計值,也可分別調用 min、max、average 或 sum 方法獲得單個的統計值,同樣,三種基本類型對應的特殊 Stream 也都包含這些方法。

6. 數據並行化

並 行 化 操 作 流 只 需 改 變 一 個 方 法 調 用。 如 果 已 經 有 一 個 Stream 對 象, 調 用 它 的parallel 方法就能讓其擁有並行操作的能力。如果想從一個集合類創建一個流,調用parallelStream 就能立即獲得一個擁有並行能力的流。

需求:並行的統計公司的所有的人員。

Set<Person> collect = departmentList.stream()
		.parallel()
		.flatMap(e -> e.getPersons().stream()).collect(Collectors.toSet());
Set<Person> collect1 = departmentList.parallelStream()
		.flatMap(e -> e.getPersons().stream()).collect(Collectors.toSet());

在底層,並行流還是沿用了 fork/join 框架。fork 遞歸式地分解問題,然後每段並行執行,最終由 join 合併結果,返回最後的值。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • JSX語法  JSX是一種JavaScript的語法擴展(eXtension),也在很多地方稱之為JavaScript XML,因為看起就是一段XML語法;  它用於描述我們的UI界面,並且其完成可以和JavaScript融合在一起使用;  它不同於Vue中的模塊語法,你不需要專門學習模塊語法 ...
  • 提起長連接,我們並不陌生,最常見的長連接非websocket莫屬了。即使沒有在項目中實際用過,至少也應該有所接觸。長連接指在一次網路通信中,客戶端與伺服器之間建立一條持久的連接,可以在多次請求和響應中重覆使用該連接。 ...
  • 本文從為什麼需要WebAssembly、WebAssembly的工作原理、哪些語言可用來創建WebAssembly模塊、WebAssembly可以用在哪裡 以及 怎麼使用 幾方面簡要介紹了webAssembly。如果之前沒有瞭解過webAssembly,可以做一些簡要的瞭解。 ...
  • # Jenkins-Pipline原理 > 本文僅探討jenkins pipline 的原理,是流水線的一個demo版本實現,不能代表Jenkins pipline的具體實現,僅供參考。 ## 1. Jenkins流水線介紹 Jenkinsfile流水線是Jenkins CI/CD工具中用來定義、構 ...
  • 在武俠世界里,“利器”通常指的是武器中的上乘、出色之物;武器對於武者的重要性不言而喻,擁有一把優秀的武器可以讓武者在戰鬥中更加得心應手,威力更強。在分散式系統追求高可用的背景下,熔斷、限流和降級這三個重要的策略可以稱得上三大利器。降級和熔斷是不是一回事?限流 與 降級呢? ...
  • 1. 介紹 阿裡巴巴 Arthas 是一個診斷工具,可以用於監視、分析和解決 Java 應用程式的問題。使用 Arthas 的一個主要優點是,我們不需要修改代碼,甚至不需要重新啟動我們想要監視的 Java 服務。 在本教程中,我們將首先安裝 Arthas,在此之後,通過一個簡單的案例來演示 Arth ...
  • Mac/Win 最新 IntelliJ IDEA 2023.2 激活破解教程,附激活碼(持續更新~),適用於 JetBrains 全家桶的所有工具。 ...
  • 部署容器是使用Docker和容器化管理應用程式更高效、易於擴展和確保跨環境一致性性能的關鍵步驟。本主題將為您概述如何部署Docker容器以創建和運行應用程式。 ## 概述 Docker容器是輕量級、可移植且自我包含的環境,可以運行應用程式及其依賴項。部署容器涉及啟動、管理和擴展這些隔離的環境,以便順 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...