扯淡
在準(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)在順豐的豐巢就職捌锭。
感謝大家的閱讀~