不要再認為Stream可讀性不高了!

来源:https://www.cnblogs.com/yulinfeng/archive/2020/03/22/12549208.html
-Advertisement-
Play Games

距離Java 8發佈已經過去了7、8年的時間,Java 14也剛剛發佈。Java 8中關於函數式編程和新增的Stream流API至今飽受“爭議”。 如果你不曾使用Stream流,那麼當你見到Stream操作時一定對它發出過鄙夷的聲音,併在心裡說出“這都寫的什麼玩意兒”。 如果你熱衷於使用Stream ...


距離Java 8發佈已經過去了7、8年的時間,Java 14也剛剛發佈。Java 8中關於函數式編程和新增的Stream流API至今飽受“爭議”。

如果你不曾使用Stream流,那麼當你見到Stream操作時一定對它發出過鄙夷的聲音,併在心裡說出“這都寫的什麼玩意兒”。

如果你熱衷於使用Stream流,那麼你一定被其他人說過它可讀性不高,甚至在codereview時被要求改用for迴圈操作,更甚至被寫入公司不規範編碼中的案例。

這篇文章將告訴你,不要再簡單地認為Stream可讀性不高了!

下麵我將圍繞以下舉例數據說明。

這裡有一些學生課程成績的數據,包含了學號、姓名、科目和成績,一個學生會包含多條不同科目的數據。

ID 學號 姓名 科目 成績
1 20200001 Kevin 語文 90
2 20200002 張三 語文 91
3 20200001 Kevin 數學 99
4 20200003 李四 語文 76
5 20200003 李四 數學 71
6 20200001 Kevin 英語 68
7 20200002 張三 數學 88
8 20200003 張三 英語 87
9 20200002 李四 英語 60

場景一:通過學號,計算一共有多少個學生?

通過學號對數據去重,如果在不藉助Stream以及第三方框架的情況下,應該能想到通過Map的key鍵不能重覆的特性迴圈遍曆數據,最後計算Map中鍵的數量。

/**
 * List列表中的元素是對象類型,使用For迴圈利用Map的key值不重覆通過對象中的學號欄位去重,計算有多少學生
 * @param students 學生信息
 */
private void calcStudentCount(List<Student> students) {
    Map<Long, Student> map = new HashMap<>();
    for (Student student : students) {
        map.put(student.getStudentNumber(), student);
    }
  int count = map.keySet().size();
  System.out.println("List列表中的元素是對象類型,使用For迴圈利用Map的key值不重覆通過對象中的學號欄位去重,計算有多少學生:" + count);
}

你可能會覺得這很簡潔清晰,但我要告訴你,這是錯的!上述代碼除了方法名calcStudentCount以外,冗餘的for迴圈樣板代碼無法流暢傳達程式員的意圖,程式員必須閱讀整個迴圈體才能理解。

接下來我們將使用Stream來準確傳達程式員的意圖。

Stream中distinct方法表示去重,這和MySQL的DISTINCT含義相同。Stream中,distinct去重是通過通過流元素中的hashCode()equals()方法去除重覆元素,如下所示通過distinct對List中的String類型元素去重。

private void useSimpleDistinct() {
    List<String> repeat = new ArrayList<>();
    repeat.add("A");
    repeat.add("B");
    repeat.add("C");
    repeat.add("A");
    repeat.add("C");
    
    List<String> notRepeating = repeat.stream().distinct().collect(Collectors.toList());
    System.out.println("List列表中的元素是簡單的數據類型:" + notRepeating.size());
}

再調用完distinct方法後,再調用collect方法對流進行最後的計算,使它成為一個新的List列表類型。

但在我們的示例中,List中的元素並不是普通的數據類型,而是一個對象,所以我們不能簡單的對它做去重,而是要先調用Stream中的map方法。

/**
 * List列表中的元素是對象類型,使用Stream利用HashMap通過對象中的學號欄位去重,計算有多少學生
 * @param students 學生信息
 */
private void useStreamByHashMap(List<Student> students) {
    long count = students.stream().map(Student::getStudentNumber).distinct().count();
    System.out.println("List列表中的元素是對象類型,使用Stream利用Map通過對象中的學號欄位去重,計算有多少學生:" + count);
}

Stream中的map方法不能簡單的和Java中的Map結構對應,準確來講,應該把Stream中的map操作理解為一個動詞,含義是歸類。既然是歸類,那麼它就會將屬於同一個類型的元素化為一類,學號相同的學生自然是屬於一類,所以使用map(Student::getStudentNumber)將學號相同的歸為一類。在通過map方法重新生成一個流過後,此時再使用distinct中間操作對流中元素的hashCode()equals()比較去除重覆元素。

另外需要註意的是,使用Stream流往往伴隨Lambda操作,有關Lambda並不是本章的重點,在這個例子中使用map操作時使用了Lambda操作中的“方法引用”——Student::getStudentNumber,語法格式為“ClassName::methodName”,完整語法是“student -> student.getStudentNumber()”,它表示在需要的時候才會調用,此處代表的是通過調用Student對象中的getStudentNumber方法進行歸類。

場景二:通過學號+姓名,計算一共有多少個學生?

傳統的方式依然是藉助Map數據結構中key鍵的特性+for迴圈實現:

/**
 * List列表中的元素是對象類型,使用For迴圈利用Map的key值不重覆通過對象中的學號+姓名欄位去重,計算有多少學生
 * @param students 學生信息
 */
private void useForByMap(List<Student> students) {
    Map<String, Student> map = new HashMap<>();
    for (Student student : students) {
      map.put(student.getStudentNumber() + student.getStudentName(), student);
    }
    int count = map.keySet().size();
    System.out.println("List列表中的元素是對象類型,使用For迴圈利用Map的key值不重覆通過對象中的學號+姓名欄位去重,計算有多少學生:" + count);

}

如果使用Stream流改動點只是map操作中的Lambda表達式:

/**
 * List列表中的元素是對象類型,使用Stream利用HashMap通過對象中的學號+姓名欄位去重,計算有多少學生
 * @param students 學生信息
 */
private void useStreamByHashMap(List<Student> students) {
    long count = students.stream().map(student -> (student.getStudentNumber() + student.getStudentName())).distinct().count();
    System.out.println("List列表中的元素是對象類型,使用Stream利用Map通過對象中的學號+姓名欄位去重,計算有多少學生:" + count);
}

前面已經提到在使用map時,如果只需要調用一個方法則可以使用Lambda表達式中的“方法引用”,但這裡需要調用兩個方法,所以只好使用Lambda表達式的完整語法“student -> (student.getStudentNumber() + student.getStudentName())”。

這個場景主要是熟悉Lambda表達式。

場景三:通過學號對學生進行分組,例如:Map<Long, List>,key=學號,value=學生成績信息

傳統的方式仍然可以通過for迴圈藉助Map實現分組:

/**
 * 藉助Map通過for迴圈分類
 * @param students 學生信息
 */
private Map<Long, List<Student>> useFor(List<Student> students) {
    Map<Long, List<Student>> map = new HashMap<>();
    for (Student student : students) {
        List<Student> list = map.get(student.getStudentNumber());
        if (list == null) {
            list = new ArrayList<>();
            map.put(student.getStudentNumber(), list);
        }
        list.add(student);
    }
    return map;
}

這種實現比場景一更為複雜,充斥著大量的樣板代碼,同樣需要程式員一行一行讀for迴圈才能理解含義,這樣的代碼真的可讀性高嗎?

來看Stream是如何解決這個問題的:

/**
 * 通過Group分組操作
 * @param students 學生信息
 * @return 學生信息,key=學號,value=學生信息
 */
private Map<Long, List<Student>> useStreamByGroup(List<Student> students) {
    Map<Long, List<Student>> map = students.stream().collect(Collectors.groupingBy(Student::getStudentNumber));
    return map;
}

一行代碼搞定分組的場景,這樣的代碼可讀性不高嗎?

場景四:過濾分數低於70分的數據,此處“過濾”的含義是排除掉低於70分的數據

傳統的for迴圈樣板代碼,想都不用想就知道直接在迴圈體中加入if判斷即可:

/**
 * 通過for迴圈過濾
 * @param  students 學生數據
 * @return 過濾後的學生數據
 */
public List<Student> useFor(List<Student> students) {
    List<Student> filterStudents = new ArrayList<>();
    for (Student student : students) {
        if (student.getScore().compareTo(70.0) > 0) {
            filterStudents.add(student);
        }
    }
    return filterStudents;
}

使用Stream流,則需要使用心得操作——filter

/**
 * 通過Stream的filter過濾操作
 * @param students 學生數據
 * @return 過濾後的學生數據
 */
public List<Student> useStream(List<Student> students) {
    List<Student> filter = students.stream().filter(student -> student.getScore().compareTo(70.0) > 0).collect(Collectors.toList());
    return filter;
}

filter中的Lambda表達式如果返回true,則包含進此次結果中,如果返回false則排除掉

以上關於Stream流的操作,你真的還認為Stream的可讀性不高嗎?

關註公眾號(CoderBuff)回覆“stream”獲取《Java8 Stream編碼實戰》PDF完整版。

這是一個能給程式員加buff的公眾號 (CoderBuff)
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • selenium基本操作 概念:基於瀏覽器自動化的模塊 :基於手機自動化的模塊的應用 環境的安裝 跟爬蟲之間的關聯? 可以實現模擬登陸 便捷的捕獲動態載入數據(可見即可得) 基本操作 導包: (web瀏覽器,driver驅動) 必須提供對應瀏覽器的驅動程式(谷歌,火狐...) "谷歌瀏覽器驅動下載地 ...
  • 我的LeetCode刷題源碼[GitHub]:https://github.com/izhoujie/Algorithmcii LeetCode 876. 鏈表的中間結點 題目 給定一個帶有頭結點 head 的非空單鏈表,返回鏈表的中間結點。 如果有兩個中間結點,則返回第二個中間結點。 示例 1: ...
  • JSP+MySQL+Java開發ssh網上預約預約掛號系統的設計與實現 需求使用SSH框架(spring+struts2+hibernate)實現一個網上預約預約掛號系統, 用戶登錄註冊登錄系統, 能按科室查看醫生, 並能夠進行預約掛號和線上留言, 後臺管理系統更能夠進行科室管理,醫生管理,預約管理 ...
  • 一、java.io.DataOutputStream;數據位元組輸出流 1.可以將記憶體中的“int i = 2;"寫入到硬碟文件裡面,寫進去的不是字元串,寫進去的是二進位數據,可以帶有類型。 package com.bjpowernode.java_learning; import java.io.* ...
  • 以前看到過NSQ這個東西,也一直沒去看。今天剛好有時間就搭建了下,簡單嘗試了下這個Go語言下的消息隊列NSQ,我這裡簡要記錄下。 其實,NSQ國內用的是比較少的,我這裡也是算瞭解這麼個東西吧 ,稍微看下源碼,學到東西而已。 NSQ簡介 NSQ是一個基於Go語言的分散式實時消息平臺, 它具有分散式、去 ...
  • 現在springboot的火熱程度已經超過了spring了,因為springboot簡單快速方便,springboot的初衷就是為了簡化spring的配置,是的開發中集成新功能時更快,簡化或者減少相關的配置。springboot的基礎是“約定大於配置”。整合了所有的框架,可以把springboot當 ...
  • ...
  • [TOC] 環境 idea 2019.1 Meavn 3.6.0 SpringBoot 2.2.5 jdk 1.8 構建eureka server 新建工程 啟動類添加註解 @EnableEurekaServer 其他配置 構建eureka client 新建工程 pom文件添加依賴,解決啟動失敗 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...