學習 Java8 函數式編程 (三)

扯淡


在準備學 Java8 之前,我以為不會很難。所以,我就決定一邊學 Java8,一邊寫博客。當我準備寫這篇博客的時候,我發現兩件都不容易。如果不是我親身體驗,我也沒法知道一篇博客的背后,作者得付出多少時間和精力。這會讓我在讀每一篇博客時,保持對作者的敬畏之心。

本篇博客會簡單介紹 Stream,以及認識是用 Stream 的 API。當我不知道這篇博客該怎么寫的時候,我想試著從 Stream 的源碼去解讀。然后,我就傻逼似的,屁顛屁顛地去看 Stream 源碼,結果可想而知,我抱著頭頂上的一堆星星去床上思考人生了。Stream 的功能非常強大,給我們這些普通的程序員帶來了極大的方便。這必然意味著,Java 的布道師們會在底層實現一堆邏輯非常復雜的代碼。所以,學習一門新技術的時候,我們應該先知道這門技術是什么,可以干什么,然后熟練掌握,最后深究它背后的實現原理。

認識 Stream


第一眼看見 Stream 這個詞的時候,會誤認為和 Java 的 I/O 流有著不可描述的關系。其實,它們倆就像潘長江和曾志偉并不是兄弟一樣,毫無關系。

其實和 Stream 有關系的是 Java 中的容器(我們經常使用的 List 和 Set 等集合)。在 Java8 之前,我們使用的是增強 for 語法糖和容器自身的 Iterator 來遍歷數據。我們先來看下清單 1 中的代碼,使用增強 for 和 Iterator 來遍歷集合。

清單 1

List<String> strList = 
    Arrays.asList("a", "b", "c", "d");

//使用增強 for 遍歷集合
for (String str : strList) {
    System.out.println(str);
}

//使用 Iterator 遍歷集合
Iterator iterator = strList.iterator();
while (iterator.hasNext()) {
    String str = iterator.next();
    System.out.println(str);
}

清單 1 中的代碼使用了增強 for 語法糖和 Iterator 來遍歷一個集合。其實使用增強 for 和 Iterator 是一回事。因為增強 for 是 Java 為簡化我們使用 Iterator 而提供的一個語法糖,它背后的實現依然是 Iterator。大家可能會發現,無論我們是使用增強 for 還是 Iteraotr 遍歷集合,都需要將集合中的元素一個個的取出來。這種將元素從集合中取出來的迭代方式叫外部迭代

現在,我們來看下清單 2 中的代碼,使用 Java8 中的 Stream 來遍歷集合。

清單 2

List<String> strList = 
    Arrays.asList("a", "b", "c",  "d");
strList.stream()
    .forEach(str -> System.out.println(str));

當大家讀完清單 2 中的代碼后,是不是似乎有點知道 Stream 是干啥的了。通過請單 2 中的代碼,我們只能看出 Stream 有遍歷的集合功能,而且寫法更加簡單,代碼的可讀性也增強了。使用 Stream 遍歷集合的方式叫內部迭代

Java8 的布道師們利用了面向對象的思想,對容器的存儲數據和遍歷數據進行了解耦。從 Java8 開始,在絕大多數情況下,我們都會使用 Stream 來遍歷集合,容器只用來存儲數據,無需關心遍歷。

按我的理解,Stream 就是一種新的迭代方式,它以一種更加簡單的方式對數據進行處理,讓代碼簡潔易懂。在接下來的內容中,大家會發現使用 Stream 處理數據的代碼,我們能夠清晰讀出代碼的意圖,而且代碼中沒有多余的臨時變量和命令式代碼。同時,使用 Stream 操作數據,并不會改變數據源(可以是容器,可以是數組,也可以是其他類型的數據源)。在多核時代,我們可以使用 Stream 寫出高效以及線程安全的代碼。但這不意味著我們需要寫線程相關的代碼,因為 Stream 在底層已經幫我們實現了。

在接下來使用 Stream 的過程中,我們只需要在 Stream 的每個函數中傳入一個函數,傳入的函數只是用來告訴 Stream 需要做什么,具體該如何做,并不需要我們關心。

使用 Stream API


準備三個類

在使用 Stream API 之前,我們先準備三個類 Artist (創作音樂的個人或團隊),Track (專輯中的一些曲目),Album (專輯,由若干曲目組成)。接下來的內容,都會圍繞這三個類展開。這三個類的具體定義如清單 3, 4, 5 所示。

清單 3

public class Artist {
    
    //藝術家的名字(例如 “縱貫線樂隊”)
    private String name;
    
    //樂隊成員(例如 “周華健"),該字段可為空
    private String members;
    
    //樂隊來自哪里(例如 “臺灣”)
    private String origin;
    
    //省略 set 和 get 方法
             
}

清單 4

public class Track {
    //曲目名稱
    private String name;
    
    //省略 set 和 get 方法

}

清單 5

public class Album {
    
    //專輯名
    private String name;
    
    //專輯上所有曲目的列表
    private List<Track> tracks;
    
    //參與創作本專輯的藝術家列表
    private List<Artist> musicians;
    
    //省略 set 和 get 方法
        
}

案例描述

問題是,找出某張專輯上所有樂隊的國籍。藝術家列表里既有個人,也有樂隊,其中樂隊名以 The 開頭。

該問題可分解為如下幾個步驟:

  • 找出專輯上的所有表演者
  • 分辨出哪些表演者是樂隊
  • 找出每個樂隊的國籍
  • 將找出的國籍放入一個集合

使用命令式代碼實現

我們先是使用命令式風格的代碼實現上述案例,代碼如清單 6 所示。

清單 6

//將專輯中的藝術家為樂隊的單獨放入一個集合 bankList
List<Artist> artistList = album.getMusicians();
List<Artist> bankList = new ArrayList<>();
for (Artist artist : artistList) {
    if (artist.getName().startsWith("The")) {
        bankList.add(artist);
    }
}
        
//找出bankList中每個樂隊的國籍,并將國籍放入originList
List<String> originList = new ArrayList<>();
for (Artist artist : bankList) {
    originList.add(artist.getOrigin());
}            

清單 6 的代碼存在如下問題:

  • 存在多余的臨時變量
  • 樣板式代碼掩蓋了關鍵代碼,代碼的可讀性很低

使用 Stream API 實現

我們現在使用 Stream API 來重寫清單 6 中的代碼,如清單 7 所示。

清單 7

Set<String> originList =
    album.getMusicians()
    .stream()
    .filter(artist -> artist.getName().startsWith("The"))
    .map(artist -> artist.getOrigin())
    .collect(toSet());

當大家看到清單 7 的代碼時候,有沒有很驚喜?反正我是為之跳躍。我先來解讀下這段代碼。其實,當大家熟悉 Stream 的 API 后,一眼就能看出這段代碼的意圖,它簡直就是將問題的每一個小步驟描述了一遍,沒有一點拖泥帶水。首先通過 getMusicians 獲取 album (專輯) 中的藝術家列表;然后使用藝術家列表構建一個 Stream,這里要說的是,所有 Collection 的子類都可以使用 stream 方法來構建一個 Stream,因為 Java8 允許接口中有 default 方法;接著調用 Stream 的 filter 方法,并告訴它篩選出 Stream 中 藝術家名字以 The 開頭的數據,將篩選出的數據組織成一個新的 Stream 返回;緊接著,調用 Stream 的 map 方法,將 Stream 中的藝術家映射為藝術家的國籍,返回新的 Stream;最后,使用 Stream 的 collect 方法生成一個 Set 集合。

經過清單6 中的代碼與清單 7 中的代碼進行比較,我想大家會一致認同,Stream 簡直太好用了,寫出來的代碼簡潔易懂。

結束語

本篇博客只是簡單介紹了 Stream 和 使用 Stream 解決問題的具體案例,在后續博客中,我會更加細致地介紹 Stream。

彩蛋


我今天要給大家介紹的是,我的學長勇哥,這個是他在開源中國的地址 https://my.oschina.net/silence88。勇哥是個完美的 Java 程序員,人帥,會做菜,彈的一手好吉他(他就是因為吉他與我嫂子相識的),會打籃球,最牛逼的還是寫代碼厲害。勇哥的博客值得一讀,你們會看到一個屌絲程序員是如何打怪升級的。勇哥是在大四的第一個學期開始自學 Java 的,他現在順豐的豐巢就職。

感謝大家的閱讀~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,837評論 18 139
  • Jav8中,在核心類庫中引入了新的概念,流(Stream)。流使得程序媛們得以站在更高的抽象層次上對集合進行操作。...
    仁昌居士閱讀 3,666評論 0 6
  • Java8 in action 沒有共享的可變數據,將方法和函數即代碼傳遞給其他方法的能力就是我們平常所說的函數式...
    鐵牛很鐵閱讀 1,259評論 1 2
  • 文/徐小木 2017-03-12 "生而為人,我們或多或少都帶著點俗氣,它是我們骨子里無法剔除的部分。...
    徐小木閱讀 744評論 3 4
  • 新學期把電腦系統格了重新安裝配置環境,windows下用的PhpStudy16種組合的Nginx+PHP7.0 n...
    小小奶狗閱讀 1,221評論 0 0