新聞中心
我們都知道 Lambda 和 Stream 是 Java 8 的兩大亮點(diǎn)功能,在前面的文章里已經(jīng)介紹過 Lambda 相關(guān)知識(shí),這次介紹下 Java 8 的 Stream 流操作。它完全不同于 java.io 包的 Input/Output Stream ,也不是大數(shù)據(jù)實(shí)時(shí)處理的 Stream 流。這個(gè) Stream 流操作是 Java 8 對集合操作功能的增強(qiáng),專注于對集合的各種高效、便利、優(yōu)雅的聚合操作。借助于 Lambda 表達(dá)式,顯著的提高編程效率和可讀性。且 Stream 提供了并行計(jì)算模式,可以簡潔的編寫出并行代碼,能充分發(fā)揮如今計(jì)算機(jī)的多核處理優(yōu)勢。

創(chuàng)新互聯(lián)堅(jiān)持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:網(wǎng)站制作、網(wǎng)站設(shè)計(jì)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時(shí)代的寧陽網(wǎng)站設(shè)計(jì)、移動(dòng)媒體設(shè)計(jì)的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!
1. Stream 流介紹
Stream 不同于其他集合框架,它也不是某種數(shù)據(jù)結(jié)構(gòu),也不會(huì)保存數(shù)據(jù),但是它負(fù)責(zé)相關(guān)計(jì)算,使用起來更像一個(gè)高級的迭代器。在之前的迭代器中,我們只能先遍歷然后在執(zhí)行業(yè)務(wù)操作,而現(xiàn)在只需要指定執(zhí)行什么操作, Stream 就會(huì)隱式的遍歷然后做出想要的操作。另外 Stream 和迭代器一樣的只能單向處理,如同奔騰長江之水一去而不復(fù)返。
由于 Stream 流提供了惰性計(jì)算和并行處理的能力,在使用并行計(jì)算方式時(shí)數(shù)據(jù)會(huì)被自動(dòng)分解成多段然后并行處理,最后將結(jié)果匯總。所以 Stream 操作可以讓程序運(yùn)行變得更加高效。
2. Stream 流概念
Stream 流的使用總是按照一定的步驟進(jìn)行,可以抽象出下面的使用流程。
數(shù)據(jù)源(source) -> 數(shù)據(jù)處理/轉(zhuǎn)換(intermedia) -> 結(jié)果處理(terminal )
2.1. 數(shù)據(jù)源
數(shù)據(jù)源(source)也就是數(shù)據(jù)的來源,可以通過多種方式獲得 Stream 數(shù)據(jù)源,下面列舉幾種常見的獲取方式。
- Collection.stream(); 從集合獲取流。
- Collection.parallelStream(); 從集合獲取并行流。
- Arrays.stream(T array) or Stream.of(); 從數(shù)組獲取流。
- BufferedReader.lines(); 從輸入流中獲取流。
- IntStream.of() ; 從靜態(tài)方法中獲取流。
- Stream.generate(); 自己生成流
2.2. 數(shù)據(jù)處理
數(shù)據(jù)處理/轉(zhuǎn)換(intermedia)步驟可以有多個(gè)操作,這步也被稱為intermedia(中間操作)。在這個(gè)步驟中不管怎樣操作,它返回的都是一個(gè)新的流對象,原始數(shù)據(jù)不會(huì)發(fā)生任何改變,而且這個(gè)步驟是惰性計(jì)算處理的,也就是說只調(diào)用方法并不會(huì)開始處理,只有在真正的開始收集結(jié)果時(shí),中間操作才會(huì)生效,而且如果遍歷沒有完成,想要的結(jié)果已經(jīng)獲取到了(比如獲取第一個(gè)值),會(huì)停止遍歷,然后返回結(jié)果。惰性計(jì)算可以顯著提高運(yùn)行效率。
數(shù)據(jù)處理演示。
數(shù)據(jù)處理/轉(zhuǎn)換操作自然不止是上面演示的過濾 filter 和 map映射兩種,另外還有 map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered 等。
2.3. 收集結(jié)果
結(jié)果處理(terminal )是流處理的最后一步,執(zhí)行完這一步之后流會(huì)被徹底用盡,流也不能繼續(xù)操作了。也只有到了這個(gè)操作的時(shí)候,流的數(shù)據(jù)處理/轉(zhuǎn)換等中間過程才會(huì)開始計(jì)算,也就是上面所說的惰性計(jì)算。結(jié)果處理也必定是流操作的最后一步。
常見的結(jié)果處理操作有 forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator 等。
下面演示了簡單的結(jié)果處理的例子。
2.4. short-circuiting
有一種 Stream 操作被稱作 short-circuiting ,它是指當(dāng) Stream 流無限大但是需要返回的 Stream 流是有限的時(shí)候,而又希望它能在有限的時(shí)間內(nèi)計(jì)算出結(jié)果,那么這個(gè)操作就被稱為short-circuiting。例如 findFirst 操作。
3. Stream 流使用
Stream 流在使用時(shí)候總是借助于 Lambda 表達(dá)式進(jìn)行操作,Stream 流的操作也有很多種方式,下面列舉的是常用的 11 種操作。
3.1. Stream 流獲取
獲取 Stream 的幾種方式在上面的 Stream 數(shù)據(jù)源里已經(jīng)介紹過了,下面是針對上面介紹的幾種獲取 Stream 流的使用示例。
3.2. forEach
forEach 是 Strean 流中的一個(gè)重要方法,用于遍歷 Stream 流,它支持傳入一個(gè)標(biāo)準(zhǔn)的 Lambda 表達(dá)式。但是它的遍歷不能通過 return/break 進(jìn)行終止。同時(shí)它也是一個(gè) terminal 操作,執(zhí)行之后 Stream 流中的數(shù)據(jù)會(huì)被消費(fèi)掉。
如輸出對象。
3.3. map / flatMap
使用 map 把對象一對一映射成另一種對象或者形式。
上面的 map 可以把數(shù)據(jù)進(jìn)行一對一的映射,而有些時(shí)候關(guān)系可能不止 1對 1那么簡單,可能會(huì)有1對多。這時(shí)可以使用 flatMap。下面演示使用 flatMap把對象扁平化展開。
3.4. filter
使用 filter 進(jìn)行數(shù)據(jù)篩選,挑選出想要的元素,下面的例子演示怎么挑選出偶數(shù)數(shù)字。
得到如下結(jié)果。
2,4,6,8,
3.5. findFirst
findFirst 可以查找出 Stream 流中的第一個(gè)元素,它返回的是一個(gè) Optional 類型,如果還不知道 Optional 類的用處,可以參考之前文章 Jdk14都要出了,還不能使用 Optional優(yōu)雅的處理空指針? 。
findFirst 方法在查找到需要的數(shù)據(jù)之后就會(huì)返回不再遍歷數(shù)據(jù)了,也因此 findFirst 方法可以對有無限數(shù)據(jù)的 Stream 流進(jìn)行操作,也可以說 findFirst 是一個(gè) short-circuiting 操作。
3.6. collect / toArray
Stream 流可以輕松的轉(zhuǎn)換為其他結(jié)構(gòu),下面是幾種常見的示例。
3.7. limit / skip
獲取或者扔掉前 n 個(gè)元素
3.8. Statistics
數(shù)學(xué)統(tǒng)計(jì)功能,求一組數(shù)組的最大值、最小值、個(gè)數(shù)、數(shù)據(jù)和、平均數(shù)等。
3.9. groupingBy
分組聚合功能,和數(shù)據(jù)庫的 Group by 的功能一致。
3.10. partitioningBy
3.11. 進(jìn)階 - 自己生成 Stream 流
上面的例子中 Stream 流是無限的,但是獲取到的結(jié)果是有限的,使用了 Limit 限制獲取的數(shù)量,所以這個(gè)操作也是 short-circuiting 操作。
4. Stream 流優(yōu)點(diǎn)
4.1. 簡潔優(yōu)雅
正確使用并且正確格式化的 Stream 流操作代碼不僅簡潔優(yōu)雅,更讓人賞心悅目。下面對比下在使用 Stream 流和不使用 Stream 流時(shí)相同操作的編碼風(fēng)格。
如果是使用 Stream 流操作。
4.2. 惰性計(jì)算
上面有提到,數(shù)據(jù)處理/轉(zhuǎn)換(intermedia) 操作 map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered 等這些操作,在調(diào)用方法時(shí)并不會(huì)立即調(diào)用,而是在真正使用的時(shí)候才會(huì)生效,這樣可以讓操作延遲到真正需要使用的時(shí)刻。
下面會(huì)舉個(gè)例子演示這一點(diǎn)。
如果沒有 惰性計(jì)算,那么很明顯會(huì)先輸出偶數(shù),然后輸出 分割線。而實(shí)際的效果是。
分割線
2
4
6
可見 惰性計(jì)算 把計(jì)算延遲到了真正需要的時(shí)候。
4.3. 并行計(jì)算
獲取 Stream 流時(shí)可以使用 parallelStream 方法代替 stream 方法以獲取并行處理流,并行處理可以充分的發(fā)揮多核優(yōu)勢,而且不增加編碼的復(fù)雜性。
下面的代碼演示了生成一千萬個(gè)隨機(jī)數(shù)后,把每個(gè)隨機(jī)數(shù)乘以2然后求和時(shí),串行計(jì)算和并行計(jì)算的耗時(shí)差異。
得到如下輸出。
效果顯而易見,代碼簡潔優(yōu)雅。
5. Stream 流建議
5.1 保證正確排版
從上面的使用案例中,可以發(fā)現(xiàn)使用 Stream 流操作的代碼非常簡潔,而且可讀性更高。但是如果不正確的排版,那么看起來將會(huì)很糟糕,比如下面的同樣功能的代碼例子,多幾層操作呢,是不是有些讓人頭大?
5.1 保證函數(shù)純度
如果想要你的 Stream 流對于每次的相同操作的結(jié)果都是相同的話,那么你必須保證 Lambda 表達(dá)式的純度,也就是下面亮點(diǎn)。
- Lambda 中不會(huì)更改任何元素。
- Lambda 中不依賴于任何可能更改的元素。
這兩點(diǎn)對于保證函數(shù)的冪等非常重要,不然你程序執(zhí)行結(jié)果可能會(huì)變得難以預(yù)測,就像下面的例子。
網(wǎng)頁標(biāo)題:看不懂同事的代碼?超強(qiáng)的 Stream 流操作姿勢還不學(xué)習(xí)一下
網(wǎng)頁地址:http://m.5511xx.com/article/cohdoso.html


咨詢
建站咨詢
