學(xué)習(xí) Java8 函數(shù)式編程 (三)

扯淡


在準(zhǔn)備學(xué) Java8 之前号坡,我以為不會(huì)很難。所以梯醒,我就決定一邊學(xué) Java8宽堆,一邊寫博客。當(dāng)我準(zhǔn)備寫這篇博客的時(shí)候茸习,我發(fā)現(xiàn)兩件都不容易畜隶。如果不是我親身體驗(yàn),我也沒法知道一篇博客的背后逮光,作者得付出多少時(shí)間和精力代箭。這會(huì)讓我在讀每一篇博客時(shí),保持對作者的敬畏之心涕刚。

本篇博客會(huì)簡單介紹 Stream嗡综,以及認(rèn)識(shí)是用 Stream 的 API。當(dāng)我不知道這篇博客該怎么寫的時(shí)候杜漠,我想試著從 Stream 的源碼去解讀极景。然后,我就傻逼似的驾茴,屁顛屁顛地去看 Stream 源碼盼樟,結(jié)果可想而知,我抱著頭頂上的一堆星星去床上思考人生了锈至。Stream 的功能非常強(qiáng)大晨缴,給我們這些普通的程序員帶來了極大的方便。這必然意味著峡捡,Java 的布道師們會(huì)在底層實(shí)現(xiàn)一堆邏輯非常復(fù)雜的代碼击碗。所以,學(xué)習(xí)一門新技術(shù)的時(shí)候们拙,我們應(yīng)該先知道這門技術(shù)是什么稍途,可以干什么,然后熟練掌握砚婆,最后深究它背后的實(shí)現(xiàn)原理械拍。

認(rèn)識(shí) Stream


第一眼看見 Stream 這個(gè)詞的時(shí)候,會(huì)誤認(rèn)為和 Java 的 I/O 流有著不可描述的關(guān)系。其實(shí)坷虑,它們倆就像潘長江和曾志偉并不是兄弟一樣甲馋,毫無關(guān)系。

其實(shí)和 Stream 有關(guān)系的是 Java 中的容器(我們經(jīng)常使用的 List 和 Set 等集合)猖吴。在 Java8 之前摔刁,我們使用的是增強(qiáng) for 語法糖和容器自身的 Iterator 來遍歷數(shù)據(jù)。我們先來看下清單 1 中的代碼海蔽,使用增強(qiáng) for 和 Iterator 來遍歷集合共屈。

清單 1

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

//使用增強(qiáng) 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 中的代碼使用了增強(qiáng) for 語法糖和 Iterator 來遍歷一個(gè)集合。其實(shí)使用增強(qiáng) for 和 Iterator 是一回事党窜。因?yàn)樵鰪?qiáng) for 是 Java 為簡化我們使用 Iterator 而提供的一個(gè)語法糖拗引,它背后的實(shí)現(xiàn)依然是 Iterator。大家可能會(huì)發(fā)現(xiàn)幌衣,無論我們是使用增強(qiáng) for 還是 Iteraotr 遍歷集合矾削,都需要將集合中的元素一個(gè)個(gè)的取出來。這種將元素從集合中取出來的迭代方式叫外部迭代豁护。

現(xiàn)在哼凯,我們來看下清單 2 中的代碼,使用 Java8 中的 Stream 來遍歷集合楚里。

清單 2

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

當(dāng)大家讀完清單 2 中的代碼后断部,是不是似乎有點(diǎn)知道 Stream 是干啥的了。通過請單 2 中的代碼班缎,我們只能看出 Stream 有遍歷的集合功能蝴光,而且寫法更加簡單,代碼的可讀性也增強(qiáng)了达址。使用 Stream 遍歷集合的方式叫內(nèi)部迭代蔑祟。

Java8 的布道師們利用了面向?qū)ο蟮乃枷耄瑢θ萜鞯拇鎯?chǔ)數(shù)據(jù)和遍歷數(shù)據(jù)進(jìn)行了解耦沉唠。從 Java8 開始疆虚,在絕大多數(shù)情況下,我們都會(huì)使用 Stream 來遍歷集合满葛,容器只用來存儲(chǔ)數(shù)據(jù)径簿,無需關(guān)心遍歷。

按我的理解纱扭,Stream 就是一種新的迭代方式,它以一種更加簡單的方式對數(shù)據(jù)進(jìn)行處理儡遮,讓代碼簡潔易懂乳蛾。在接下來的內(nèi)容中,大家會(huì)發(fā)現(xiàn)使用 Stream 處理數(shù)據(jù)的代碼,我們能夠清晰讀出代碼的意圖肃叶,而且代碼中沒有多余的臨時(shí)變量和命令式代碼蹂随。同時(shí),使用 Stream 操作數(shù)據(jù)因惭,并不會(huì)改變數(shù)據(jù)源(可以是容器岳锁,可以是數(shù)組,也可以是其他類型的數(shù)據(jù)源)蹦魔。在多核時(shí)代激率,我們可以使用 Stream 寫出高效以及線程安全的代碼。但這不意味著我們需要寫線程相關(guān)的代碼勿决,因?yàn)?Stream 在底層已經(jīng)幫我們實(shí)現(xiàn)了乒躺。

在接下來使用 Stream 的過程中,我們只需要在 Stream 的每個(gè)函數(shù)中傳入一個(gè)函數(shù)低缩,傳入的函數(shù)只是用來告訴 Stream 需要做什么嘉冒,具體該如何做,并不需要我們關(guān)心咆繁。

使用 Stream API


準(zhǔn)備三個(gè)類

在使用 Stream API 之前讳推,我們先準(zhǔn)備三個(gè)類 Artist (創(chuàng)作音樂的個(gè)人或團(tuán)隊(duì)),Track (專輯中的一些曲目)玩般,Album (專輯银觅,由若干曲目組成)。接下來的內(nèi)容壤短,都會(huì)圍繞這三個(gè)類展開设拟。這三個(gè)類的具體定義如清單 3, 4, 5 所示。

清單 3

public class Artist {
    
    //藝術(shù)家的名字(例如 “縱貫線樂隊(duì)”)
    private String name;
    
    //樂隊(duì)成員(例如 “周華健")久脯,該字段可為空
    private String members;
    
    //樂隊(duì)來自哪里(例如 “臺(tái)灣”)
    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;
    
    //參與創(chuàng)作本專輯的藝術(shù)家列表
    private List<Artist> musicians;
    
    //省略 set 和 get 方法
        
}

案例描述

問題是纳胧,找出某張專輯上所有樂隊(duì)的國籍。藝術(shù)家列表里既有個(gè)人帘撰,也有樂隊(duì)跑慕,其中樂隊(duì)名以 The 開頭。

該問題可分解為如下幾個(gè)步驟:

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

使用命令式代碼實(shí)現(xiàn)

我們先是使用命令式風(fēng)格的代碼實(shí)現(xiàn)上述案例摧找,代碼如清單 6 所示核行。

清單 6

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

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

  • 存在多余的臨時(shí)變量
  • 樣板式代碼掩蓋了關(guān)鍵代碼蹬耘,代碼的可讀性很低

使用 Stream API 實(shí)現(xiàn)

我們現(xiàn)在使用 Stream API 來重寫清單 6 中的代碼芝雪,如清單 7 所示。

清單 7

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

當(dāng)大家看到清單 7 的代碼時(shí)候综苔,有沒有很驚喜惩系?反正我是為之跳躍位岔。我先來解讀下這段代碼。其實(shí)堡牡,當(dāng)大家熟悉 Stream 的 API 后抒抬,一眼就能看出這段代碼的意圖,它簡直就是將問題的每一個(gè)小步驟描述了一遍晤柄,沒有一點(diǎn)拖泥帶水擦剑。首先通過 getMusicians 獲取 album (專輯) 中的藝術(shù)家列表;然后使用藝術(shù)家列表構(gòu)建一個(gè) Stream芥颈,這里要說的是惠勒,所有 Collection 的子類都可以使用 stream 方法來構(gòu)建一個(gè) Stream,因?yàn)?Java8 允許接口中有 default 方法浇借;接著調(diào)用 Stream 的 filter 方法捉撮,并告訴它篩選出 Stream 中 藝術(shù)家名字以 The 開頭的數(shù)據(jù),將篩選出的數(shù)據(jù)組織成一個(gè)新的 Stream 返回妇垢;緊接著巾遭,調(diào)用 Stream 的 map 方法,將 Stream 中的藝術(shù)家映射為藝術(shù)家的國籍闯估,返回新的 Stream灼舍;最后,使用 Stream 的 collect 方法生成一個(gè) Set 集合涨薪。

經(jīng)過清單6 中的代碼與清單 7 中的代碼進(jìn)行比較骑素,我想大家會(huì)一致認(rèn)同,Stream 簡直太好用了刚夺,寫出來的代碼簡潔易懂献丑。

結(jié)束語

本篇博客只是簡單介紹了 Stream 和 使用 Stream 解決問題的具體案例,在后續(xù)博客中侠姑,我會(huì)更加細(xì)致地介紹 Stream创橄。

彩蛋


我今天要給大家介紹的是,我的學(xué)長勇哥莽红,這個(gè)是他在開源中國的地址 https://my.oschina.net/silence88妥畏。勇哥是個(gè)完美的 Java 程序員,人帥安吁,會(huì)做菜醉蚁,彈的一手好吉他(他就是因?yàn)榧c我嫂子相識(shí)的),會(huì)打籃球鬼店,最牛逼的還是寫代碼厲害网棍。勇哥的博客值得一讀,你們會(huì)看到一個(gè)屌絲程序員是如何打怪升級的妇智。勇哥是在大四的第一個(gè)學(xué)期開始自學(xué) Java 的滥玷,他現(xiàn)在順豐的豐巢就職捌锭。

感謝大家的閱讀~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市罗捎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拉盾,老刑警劉巖桨菜,帶你破解...
    沈念sama閱讀 211,948評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異捉偏,居然都是意外死亡倒得,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評論 3 385
  • 文/潘曉璐 我一進(jìn)店門夭禽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來霞掺,“玉大人,你說我怎么就攤上這事讹躯∑斜颍” “怎么了?”我有些...
    開封第一講書人閱讀 157,490評論 0 348
  • 文/不壞的土叔 我叫張陵潮梯,是天一觀的道長骗灶。 經(jīng)常有香客問我,道長秉馏,這世上最難降的妖魔是什么耙旦? 我笑而不...
    開封第一講書人閱讀 56,521評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮萝究,結(jié)果婚禮上免都,老公的妹妹穿的比我還像新娘。我一直安慰自己帆竹,他們只是感情好绕娘,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著馆揉,像睡著了一般业舍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上升酣,一...
    開封第一講書人閱讀 49,842評論 1 290
  • 那天舷暮,我揣著相機(jī)與錄音,去河邊找鬼噩茄。 笑死下面,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的绩聘。 我是一名探鬼主播沥割,決...
    沈念sama閱讀 38,997評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼耗啦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了机杜?” 一聲冷哼從身側(cè)響起帜讲,我...
    開封第一講書人閱讀 37,741評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎椒拗,沒想到半個(gè)月后似将,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,203評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蚀苛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評論 2 327
  • 正文 我和宋清朗相戀三年在验,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片堵未。...
    茶點(diǎn)故事閱讀 38,673評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡腋舌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出渗蟹,到底是詐尸還是另有隱情块饺,我是刑警寧澤,帶...
    沈念sama閱讀 34,339評論 4 330
  • 正文 年R本政府宣布雌芽,位于F島的核電站刨沦,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏膘怕。R本人自食惡果不足惜想诅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望岛心。 院中可真熱鬧来破,春花似錦、人聲如沸忘古。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽髓堪。三九已至送朱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間干旁,已是汗流浹背驶沼。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留争群,地道東北人回怜。 一個(gè)月前我還...
    沈念sama閱讀 46,394評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像换薄,于是被迫代替她去往敵國和親玉雾。 傳聞我的和親對象是個(gè)殘疾皇子翔试,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評論 2 349

推薦閱讀更多精彩內(nèi)容

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