Java?8中?Stream小知識(shí)小技巧方法梳理
前言
上篇只是簡(jiǎn)單的動(dòng)手操作操作了流(stream),那 stream 到底是什么呢?
官方的簡(jiǎn)短定義:“從支持?jǐn)?shù)據(jù)處理操作的源生成的元素序列”
分成三部分:
- 元素序列:你可以簡(jiǎn)單將它類比于一樣,不過(guò)集合說(shuō)的是數(shù)據(jù)的集合,而 stream 重點(diǎn)在于表達(dá)計(jì)算。如我們之前說(shuō)到的 filter、map、sorted、limit等等
- 源:昨天我提到,如果了解過(guò) Liunx 管道命令的朋友們,會(huì)知道,Liunx 的管道命令中的前一條命令的結(jié)果(輸出流)就是執(zhí)行下一條命令的輸入流。 stream流其實(shí)也是類似的,每次執(zhí)行完一個(gè) filter、sorted 等等它們的返回值也是 stream 流,然后作為下一個(gè)操作的輸入流,這種。
- 數(shù)據(jù)處理:這里說(shuō)的處理,就是那些filter、map、sorted、limit等。
你可以把它理解為概念上或是形式上的數(shù)據(jù)結(jié)構(gòu),而不是具體的。
只能遍歷的一次 Stream
Stream 流和 迭代器一樣,它只能夠迭代一次。
當(dāng)它遍歷完的時(shí)候,我們就稱它已經(jīng)消費(fèi)完了。如果還想重新執(zhí)行操作,那么就只能從原來(lái)的地方再獲取一個(gè)流啦。
?/** ? ? ? * 圖個(gè)樂(lè)子 ? ? ? */ ?public static List<String> list = Arrays.asList("醫(yī)醫(yī)","一一","花花","菡菡","春春","寧姐","..."); ?? ?public static void main(String[] args) { ? ? ?Stream<String> stream = list.stream(); ? ? ?stream.forEach(System.out::println); ? ? ?stream.forEach(System.out::println); ?}
當(dāng)執(zhí)行第二句stream.forEach(System.out::println);
時(shí),會(huì)爆出如下異常:
?Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed ? at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279) ? at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580) ? at com.nzc.stream02.Stream02Demo.main(Stream02Demo.java:25)
stream has already been operated upon or closed :流已經(jīng)被操作完或者關(guān)閉啦~
那么為什么流只能遍歷一次呢?
我的回答肯定是 Java 中的設(shè)計(jì)是如此。
我們先來(lái)回憶一下我昨天沒(méi)提到的一個(gè)知識(shí)點(diǎn):
隨便舉一個(gè)例子,然后我們將 filter、map、limit、collect 都使用上。
?List<Student> students = new ArrayList<>(); ?? ?students.add(new Student("學(xué)生A", "大學(xué)1", 18, 98.0)); ?students.add(new Student("學(xué)生A", "大學(xué)1", 18, 91.0)); ?students.add(new Student("學(xué)生A", "大學(xué)1", 18, 90.0)); ?students.add(new Student("學(xué)生A", "大學(xué)1", 18, 76.0)); ?students.add(new Student("學(xué)生B", "大學(xué)1", 18, 91.0)); ?students.add(new Student("學(xué)生C", "大學(xué)1", 19, 65.0)); ?students.add(new Student("學(xué)生D", "大學(xué)2", 20, 80.0)); ?students.add(new Student("學(xué)生E", "大學(xué)2", 21, 78.0)); ?students.add(new Student("學(xué)生F", "大學(xué)2", 20, 67.0)); ?students.add(new Student("學(xué)生G", "大學(xué)3", 22, 87.0)); ?students.add(new Student("學(xué)生H", "大學(xué)3", 23, 79.0)); ?students.add(new Student("學(xué)生I", "大學(xué)3", 19, 92.0)); ?students.add(new Student("學(xué)生J", "大學(xué)4", 20, 84.0)); ?List<String> collect = students.stream().filter(student -> student.getAge() > 20).map(student -> student.getName()).limit(3).collect(Collectors.toList());
昨天我沒(méi)有具體說(shuō)明這幾步是如何執(zhí)行的。
畫(huà)張圖來(lái)簡(jiǎn)要的說(shuō)明一下:
- filter, 根據(jù)條件篩選掉一些元素
- map,將元素轉(zhuǎn)換成其他的形式
- limit,將流進(jìn)行截?cái)?,只獲取其中的一部分
- collect,這里主要就是將流轉(zhuǎn)換成其他的集合對(duì)象,這一步也是流的終端操作,之后也會(huì)聊到。
在這里你可以看到Java 8中的 stream 的設(shè)計(jì),就是只能遍歷一次,并且當(dāng)執(zhí)行完終端操作的時(shí)候,流也是已經(jīng)關(guān)閉或者說(shuō)是消費(fèi)完了。
流操作
上面剛剛提到了終端操作,這也是一個(gè)小小的知識(shí)點(diǎn)。
Stream 的api提供了不少方法,諸如 filter、sotred、limit、map、collect等。
其中 filter、sotred、limit、map 它們的返回結(jié)果仍然是一個(gè)stream 流,這些操作,我們稱之為中間操作。并且它們之間的連接可以形成一個(gè)流水線任務(wù)。
而 collect 則是一個(gè)終端操作,它會(huì)觸發(fā)并執(zhí)行流水線任務(wù),最終關(guān)閉它。
中間操作
就是上面說(shuō)的 filter、map等等,但是需要注意的是,如果流水線上沒(méi)有一個(gè)終端操作,那么你寫(xiě)的filter、map什么的,并不會(huì)執(zhí)行任何處理。
通過(guò)一個(gè)簡(jiǎn)單的案例,來(lái)看一看stream流中的lambda的函數(shù)的執(zhí)行流程。
? ? ?static List<Student> students = new ArrayList<>(); ?? ? ? ?static { ? ? ? ? ?students.add(new Student("學(xué)生A", "大學(xué)1", 18, 98.0)); ? ? ? ? ?students.add(new Student("學(xué)生A", "大學(xué)1", 18, 91.0)); ? ? ? ? ?students.add(new Student("學(xué)生A", "大學(xué)1", 18, 90.0)); ? ? ? ? ?students.add(new Student("學(xué)生A", "大學(xué)1", 18, 76.0)); ? ? ? ? ?students.add(new Student("學(xué)生B", "大學(xué)1", 18, 91.0)); ? ? ? ? ?students.add(new Student("學(xué)生C", "大學(xué)1", 19, 65.0)); ? ? ? ? ?students.add(new Student("學(xué)生D", "大學(xué)2", 20, 80.0)); ? ? ? ? ?students.add(new Student("學(xué)生E", "大學(xué)2", 21, 78.0)); ? ? ? ? ?students.add(new Student("學(xué)生F", "大學(xué)2", 20, 67.0)); ? ? ? ? ?students.add(new Student("學(xué)生G", "大學(xué)3", 22, 87.0)); ? ? ? ? ?students.add(new Student("學(xué)生H", "大學(xué)3", 23, 79.0)); ? ? ? ? ?students.add(new Student("學(xué)生I", "大學(xué)3", 19, 92.0)); ? ? ? ? ?students.add(new Student("學(xué)生J", "大學(xué)4", 20, 84.0)); ? ? }
?public static void main(String[] args) { ? ? ?List<String> collect = students.stream().filter(student -> { ? ? ? ? ?System.out.println("filter==>" + student.getName() + ":" + student.getAge()); ? ? ? ? ?return student.getAge() > 20; ? ? }).map(student -> { ? ? ? ? ?System.out.println("map==>" + student.getName() + ":" + student.getAge()); ? ? ? ? ?return student.getName(); ? ? }).collect(Collectors.toList()); ?? ?}
輸出:
filter==>學(xué)生A:18
map==>學(xué)生A:18
filter==>學(xué)生A:18
map==>學(xué)生A:18
filter==>學(xué)生A:18
map==>學(xué)生A:18
filter==>學(xué)生A:18
map==>學(xué)生A:18
filter==>學(xué)生B:18
map==>學(xué)生B:18
filter==>學(xué)生C:19
map==>學(xué)生C:19
filter==>學(xué)生D:20
filter==>學(xué)生E:21
filter==>學(xué)生F:20
filter==>學(xué)生G:22
filter==>學(xué)生H:23
filter==>學(xué)生I:19
map==>學(xué)生I:19
filter==>學(xué)生J:20
也許到這里還看的不明顯,我們?cè)偌由弦粋€(gè) limit(3),來(lái)看一下這次會(huì)如何輸出
?List<String> collect = students.stream().filter(student -> { ? ? ?System.out.println("filter==>" + student.getName() + ":" + student.getAge()); ? ? ?return student.getAge() < 20; ?}).map(student -> { ? ? ?System.out.println("map==>" + student.getName() + ":" + student.getAge()); ? ? ?return student.getName(); ?}).limit(3).collect(Collectors.toList()); ?? ?System.out.println(collect);
輸出結(jié)果:
?filter==>學(xué)生A:18 ?map==>學(xué)生A:18 ?filter==>學(xué)生A:18 ?map==>學(xué)生A:18 ?filter==>學(xué)生A:18 ?map==>學(xué)生A:18 ?[學(xué)生A, 學(xué)生A, 學(xué)生A]
1、實(shí)際上這里利用到的 limit(n) 就是俗稱的短路技巧,像我們剛學(xué) &&、| | 語(yǔ)法時(shí)一樣,如果&&條件為false,則會(huì)短路,不執(zhí)行&& 之后的表達(dá)式,這里的 limit 你可以看作是 || 的表達(dá)。
Stream 流在內(nèi)部迭代中主動(dòng)幫我們進(jìn)行了優(yōu)化。
2、另外看似 filter 和 map 是兩個(gè)獨(dú)立的函數(shù),但實(shí)際上他們都是在一次遍歷中所進(jìn)行的。這一點(diǎn)從輸出中也可看出。
終端操作
終端操作,就是觸發(fā)并執(zhí)行流水線任務(wù),最終關(guān)閉流的操作。
像之前寫(xiě)的collect(Collectors.toList())
和 forEach(System.out::println)
都是終端操作。
終端操作其結(jié)果返回的都是流的值,collect返回各種各樣的集合啦,
forEach 返回的就是 void 值。
中間操作:
操 作 | 類型 | 結(jié)果 | 操作參數(shù) | |
---|---|---|---|---|
filter | 中間 | Stream | Predicate | T -> boolean |
map | 中間 | Stream | Stream Function | T -> R |
limit | 中間 | Stream | ||
sorted | 中間 | Stream | Comparator | (T, T) -> int |
distinct | 中間 | Stream |
終端操作:
操 作 | 類 型 | 目 的 |
---|---|---|
forEach | 終端 | 消費(fèi)流中的每個(gè)元素并對(duì)其應(yīng)用 Lambda。這一操作返回 void |
count | 終端 | 返回流中元素的個(gè)數(shù)。這一操作返回 long |
collect | 終端 | 把流歸約成一個(gè)集合,比如 List、Map 甚至是 Integer。 |
到此這篇關(guān)于Java 8中 Stream小知識(shí)小技巧方法梳理的文章就介紹到這了,更多相關(guān)Java 8 Stream 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
通過(guò)FeignClient如何獲取文件流steam?is?close問(wèn)題
這篇文章主要介紹了通過(guò)FeignClient如何獲取文件流steam?is?close問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06Java微信公眾平臺(tái)開(kāi)發(fā)(13) 微信JSSDK中Config配置
這篇文章主要為大家詳細(xì)介紹了Java微信公眾平臺(tái)開(kāi)發(fā)第十三步,微信JSSDK中Config配置,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04SpringBoot框架實(shí)現(xiàn)切換啟動(dòng)開(kāi)發(fā)環(huán)境和測(cè)試環(huán)境
這篇文章主要介紹了SpringBoot框架實(shí)現(xiàn)切換啟動(dòng)開(kāi)發(fā)環(huán)境和測(cè)試環(huán)境,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12Java利用MD5加鹽實(shí)現(xiàn)對(duì)密碼進(jìn)行加密處理
在開(kāi)發(fā)的時(shí)候,有一些敏感信息是不能直接通過(guò)明白直接保存到數(shù)據(jù)庫(kù)的。最經(jīng)典的就是密碼了。如果直接把密碼以明文的形式入庫(kù),不僅會(huì)泄露用戶的隱私,對(duì)系統(tǒng)也是極其的不厲。本文就來(lái)和大家介紹一下如何對(duì)密碼進(jìn)行加密處理,感興趣的可以了解一下2023-02-02