1. Stream概述?
JDK文檔:
A sequence of elements supporting sequential and parallel aggregate operations.
中文翻譯:
Stream是元素的集合到腥,可以支持順序和并行的對(duì)原Stream進(jìn)行匯聚的操作朵逝;
Stream代表數(shù)據(jù)流,流中的數(shù)據(jù)元素的數(shù)量可能是有限的乡范,也可能是無(wú)限的配名。
Java為什么要引入Stream?這個(gè)問題可以從側(cè)面更好的了解Stream的概念
- 通過函數(shù)式編程的方式可以將將復(fù)雜的數(shù)據(jù)處理過程變得簡(jiǎn)單明了晋辆,那么這個(gè)和Stream有什么關(guān)系渠脉?本質(zhì)上Streams是Monads。
Monad就是一種設(shè)計(jì)模式瓶佳,表示將一個(gè)運(yùn)算過程芋膘,通過函數(shù)拆解成互相連接的多個(gè)步驟。你只要提供下一步運(yùn)算所需的函數(shù)霸饲,整個(gè)運(yùn)算就會(huì)自動(dòng)進(jìn)行下去为朋。
- 提供串行和并行兩種模式進(jìn)行匯聚操作,并發(fā)模式能夠充分利用多核處理器的優(yōu)勢(shì)
Stream相關(guān)概念
- 一系列元素:Stream對(duì)一組有特定類型的元素提供了一個(gè)接口厚脉。但是Stream并不真正存儲(chǔ)元素习寸,元素根據(jù)需求被計(jì)算出結(jié)果。
- 源:Stream可以處理任何一種數(shù)據(jù)提供源傻工,比如結(jié)合霞溪、數(shù)組孵滞,或者I/O資源。
- 聚合操作:Stream支持類似SQL一樣的操作鸯匹,常規(guī)的操作都是函數(shù)式編程語(yǔ)言坊饶,比如filter,map忽你,reduce幼东,find间狂,match蜒简,sorted店归,等等县昂。
Stream操作還具備兩個(gè)基本特性使它與集合操作不同:
- 管道:許多Stream操作會(huì)返回一個(gè)stream對(duì)象本身特漩。這就允許所有操作可以連接起來(lái)形成一個(gè)更大的管道憔杨。這就就可以進(jìn)行特定的優(yōu)化了趁啸,比如懶加載和短回路巨缘,我們將在下面介紹尿赚。
- 內(nèi)部迭代:和集合的顯式迭代(外部迭代)相比散庶,Stream操作不需要我們手動(dòng)進(jìn)行迭代。
總結(jié)Stream的特點(diǎn)
- 不存儲(chǔ)數(shù)據(jù)凌净。流是基于數(shù)據(jù)源的對(duì)象悲龟,它本身不存儲(chǔ)數(shù)據(jù)元素,而是通過管道將數(shù)據(jù)源的元素傳遞給操作冰寻。
- 函數(shù)式編程须教。對(duì)stream的任何修改都不會(huì)修改背后的數(shù)據(jù)源,比如對(duì)stream執(zhí)行過濾操作并不會(huì)刪除被過濾的元素斩芭,而是會(huì)產(chǎn)生一個(gè)不包含被過濾元素的新stream轻腺。
- 延遲操作。流的很多操作如filter,map等中間操作是延遲執(zhí)行的划乖,只有到終點(diǎn)操作才會(huì)將操作順序執(zhí)行贬养。
- 可以解綁。對(duì)于無(wú)限數(shù)量的流琴庵,有些操作是可以在有限的時(shí)間完成的误算,比如limit(n) 或 findFirst(),這些操作可是實(shí)現(xiàn)"短路"(Short-circuiting)迷殿,訪問到有限的元素后就可以返回尉桩。
- 純消費(fèi)。流的元素只能訪問一次贪庙,類似Iterator蜘犁,操作沒有回頭路,如果你想從頭重新訪問流的元素止邮,需要重新生成一個(gè)新的流这橙。
2. Stream的使用
2.1 流的操作類型
對(duì)stream的操作分為為兩類奏窑,中間操作(intermediate operations)和結(jié)束操作(terminal operations)
- 中間操作總是會(huì)惰式執(zhí)行,調(diào)用中間操作只會(huì)生成一個(gè)標(biāo)記了該操作的新stream屈扎,僅此而已埃唯。
- 結(jié)束操作會(huì)觸發(fā)實(shí)際計(jì)算,計(jì)算發(fā)生時(shí)會(huì)把所有中間操作積攢的操作以pipeline的方式執(zhí)行鹰晨,這樣可以減少迭代次數(shù)墨叛。計(jì)算完成之后stream就會(huì)失效。
還有一種操作被稱為 short-circuiting模蜡。用以指:
對(duì)于一個(gè) intermediate 操作漠趁,如果它接受的是一個(gè)無(wú)限大(infinite/unbounded)的 Stream,但返回一個(gè)有限的新 Stream忍疾。對(duì)于一個(gè) terminal 操作闯传,如果它接受的是一個(gè)無(wú)限大的 Stream,但能在有限的時(shí)間計(jì)算出結(jié)果卤妒。
2.2 使用Stream的步驟
創(chuàng)建Stream -> 轉(zhuǎn)換Stream每次轉(zhuǎn)換原有Stream對(duì)象不改變甥绿,返回一個(gè)新的Stream對(duì)象(可以有多次轉(zhuǎn)換) ->對(duì)Stream進(jìn)行聚合(Reduce)操作,獲取想要的結(jié)果
2.3 Stream創(chuàng)建
- 通過Collection的stream()方法或者parallelStream()则披,比如Arrays.asList(1,2,3).stream()。
- 使用流的靜態(tài)方法骄呼,比如Stream.of(Object[]), IntStream.range(int, int) 或者 Stream.iterate(Object, UnaryOperator)澄峰,如Stream.iterate(0, n -> n * 2)堂竟,或者generate(Supplier<T> s)如Stream.generate(Math::random)席楚。
- BufferedReader.lines()從文件中獲得行的流垮斯。
- Files類的操作路徑的方法抛寝,如list晶府、find爷绘、walk等。
- 隨機(jī)數(shù)流Random.ints()
- 通過Arrays.stream(Object[])方法, 比如Arrays.stream(new int[]{1,2,3})。
- 其它一些類提供了創(chuàng)建流的方法,如BitSet.stream(), Pattern.splitAsStream(java.lang.CharSequence), 和 JarFile.stream()镣衡。
追蹤到底層其實(shí)都是使用StreamSupport類望浩,它提供了將Spliterator轉(zhuǎn)換成流的方法吆视。至于它的內(nèi)部細(xì)節(jié)在下片文章介紹搔弄。
2.4 中間操作
中間操作會(huì)返回一個(gè)新的流,但是操作是延遲執(zhí)行的(lazy),它不會(huì)修改原始的數(shù)據(jù)源顾彰,而且是由在終點(diǎn)操作開始的時(shí)候才真正開始執(zhí)行厕隧。
這個(gè)Scala集合的轉(zhuǎn)換操作不同吁讨,Scala集合轉(zhuǎn)換操作會(huì)生成一個(gè)新的中間集合髓迎,顯而易見Java的這種設(shè)計(jì)會(huì)減少中間對(duì)象的生成。
操作類型 | 方法 |
---|---|
中間操作 | concat() distinct() filter() flatMap() limit() map() peek() skip() sorted() parallel() sequential() unordered() |
區(qū)分中間操作和結(jié)束操作最簡(jiǎn)單的方法建丧,就是看方法的返回值排龄,返回值為stream的大都是中間操作,否則是結(jié)束操作茶鹃。
distinct()
Stream<T> distinct();
對(duì)于Stream中包含的元素進(jìn)行去重操作(去重邏輯依賴元素的equals方法),新生成的Stream中沒有重復(fù)的元素艰亮;
filter()
Stream<T> filter(Predicate<? super T> predicate);
對(duì)于Stream中包含的元素使用給定的predicate過濾函數(shù)進(jìn)行過濾操作闭翩,新生成的Stream只包含符合條件的元素。
map()
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
對(duì)于Stream中包含的元素使用給定的轉(zhuǎn)換函數(shù)進(jìn)行轉(zhuǎn)換操作迄埃,對(duì)每個(gè)元素按照某種操作進(jìn)行轉(zhuǎn)換疗韵,轉(zhuǎn)換前后Stream中元素的個(gè)數(shù)不會(huì)改變,但元素的類型取決于轉(zhuǎn)換之后的類型侄非。
這個(gè)方法有三個(gè)對(duì)于原始類型的變種方法蕉汪,分別是:mapToInt流译,mapToLong和mapToDouble。比如mapToInt就是把原始Stream轉(zhuǎn)換成一個(gè)新的Stream者疤,這個(gè)新生成的Stream中的元素都是int類型福澡。之所以會(huì)有這樣三個(gè)變種方法,可以免除自動(dòng)裝箱/拆箱的額外消耗驹马;
flatMap()
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
和map類似革砸,不同的是其每個(gè)元素轉(zhuǎn)換得到的是Stream對(duì)象,會(huì)把子Stream中的元素壓縮到父集合中糯累;
peek()
Stream<T> peek(Consumer<? super T> action);
生成一個(gè)包含原Stream的所有元素的新Stream算利,同時(shí)會(huì)提供一個(gè)消費(fèi)函數(shù)(Consumer實(shí)例),新Stream每個(gè)元素被消費(fèi)的時(shí)候都會(huì)執(zhí)行給定的消費(fèi)函數(shù)
skip()
Stream<T> skip(long n);
返回一個(gè)丟棄原Stream的前N個(gè)元素后剩下元素組成的新Stream泳姐,如果原Stream中包含的元素個(gè)數(shù)小于N效拭,那么返回空Stream;
limit()
對(duì)一個(gè)Stream進(jìn)行截?cái)嗖僮髋置耄@取其前N個(gè)元素缎患。如果原Stream中包含的元素個(gè)數(shù)小于N,那就獲取其所有的元素扒怖,這是一個(gè)short-circuiting 操作较锡。
下面統(tǒng)一實(shí)例代碼:
Arrays.asList(1, 1, null, 2, 3, 4, null, 5, 6, 7, 8, 9, 10)
.stream()
.distinct()
.filter(t -> t != null && t > 9)
.map(t -> t * 10)
.flatMap(t -> Stream.of(t, t + 1))
.skip(1)
.forEach(System.out::println);
2.5 終結(jié)操作
操作類型 | 方法 |
---|---|
結(jié)束操作 | allMatch() anyMatch() collect() count() findAny() findFirst() forEach() forEachOrdered() max() min() noneMatch() reduce() toArray() |
Match
public boolean allMatch(Predicate<? super T> predicate)
public boolean anyMatch(Predicate<? super T> predicate)
public boolean noneMatch(Predicate<? super T> predicate)
這一組方法用來(lái)檢查流中的元素是否滿足斷言。
- allMatch只有在所有的元素都滿足斷言時(shí)才返回true,否則flase,流為空時(shí)總是返回true
- anyMatch只有在任意一個(gè)元素滿足斷言時(shí)就返回true,否則flase,
- noneMatch只有在所有的元素都不滿足斷言時(shí)才返回true,否則flase
count
count方法返回流中的元素的數(shù)量盗痒。它實(shí)現(xiàn)為:
forEach/forEachOrdered
forEach遍歷流的每一個(gè)元素蚂蕴,執(zhí)行指定的action。和peek方法不同俯邓。這個(gè)方法不擔(dān)保按照流的encounter order順序執(zhí)行骡楼,如果對(duì)于有序流按照它的encounter order順序執(zhí)行,你可以使用forEachOrdered方法稽鞭。
max/min
max返回流中的最大值鸟整,
min返回流中的最小值。
toArray()
將流中的元素放入到一個(gè)數(shù)組中朦蕴。
總結(jié):
終結(jié)操作也叫匯聚操作篮条,它接受一個(gè)元素序列為輸入,反復(fù)使用某個(gè)合并操作吩抓,把序列中的元素合并成一個(gè)匯總的結(jié)果涉茧。比如查找一個(gè)數(shù)字列表的總和或者最大值,或者把這些數(shù)字累積成一個(gè)List對(duì)象疹娶。Stream接口有一些通用的匯聚操作伴栓,比如reduce()和collect();也有一些特定用途的匯聚操作,比如sum(),max()和count()钳垮。
注意:sum方法不是所有的Stream對(duì)象都有的惑淳,只有IntStream、LongStream和DoubleStream是實(shí)例才有饺窿。
匯聚操作可以分為以下兩類:
可變匯聚:把輸入的元素們累積到一個(gè)可變的容器中歧焦,如collect。
其他匯聚:除去可變匯聚剩下的短荐,一般都不是通過反復(fù)修改某個(gè)可變對(duì)象倚舀,而是通過把前一次的匯聚結(jié)果當(dāng)成下一次的入?yún)ⅲ磸?fù)如此忍宋。比如reduce痕貌,count,allMatch糠排。
collect和ruduce比較重要舵稠,單獨(dú)一節(jié)講述。
3. reduce
作用是把 Stream 元素組合起來(lái)入宦。它提供一個(gè)起始值(種子)哺徊,然后依照運(yùn)算規(guī)則(BinaryOperator),和前面 Stream 的第一個(gè)乾闰、第二個(gè)落追、第 n 個(gè)元素組合。從這個(gè)意義上說涯肩,字符串拼接轿钠、數(shù)值的 sum、min病苗、max疗垛、average 都是特殊的 reduce。例如 Stream 的 sum 就相當(dāng)于
Integer sum = integers.reduce(0, (a, b) -> a+b);
//或者
Integer sum = integers.reduce(0, Integer::sum);
下面看它的定義:
Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)
<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
舉個(gè)例子
Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.stream()
.reduce((a, b) -> a + b)
.ifPresent(System.out::println);
System.out.println(Stream.of("A", "B", "C", "D")
.reduce("Str", (a, b) -> a + "-" + b));
Stream.of("I", "love", "you")
.reduce(0,// 初始值
(sum, str) -> sum + str.length(),// 累加操作
(a, b) -> 0); //并行stream才會(huì)用到
4. collect
可變匯聚對(duì)應(yīng)的只有一個(gè)方法:collect硫朦,正如其名字顯示的贷腕,它可以把Stream中的要有元素收集到一個(gè)結(jié)果容器中(比如Collection)∫д梗看一下它的定義:
<R> R collect(Supplier<R> supplier,
ObjIntConsumer<R> accumulator,
BiConsumer<R, R> combiner)
先來(lái)看看這三個(gè)參數(shù)的含義: supplier是一個(gè)工廠函數(shù)泽裳,用來(lái)生成一個(gè)新的容器, accumulator用來(lái)把Stream中的元素添加到結(jié)果容器中破婆,BiConsumer<R, R> combiner參數(shù)用來(lái)把中間狀態(tài)的多個(gè)結(jié)果容器合并成為一個(gè)(并行的時(shí)候會(huì)用到)
還有一個(gè)重載函數(shù),參數(shù)是Collector類型涮总,三個(gè)參數(shù)太麻煩,收集器Collector就是對(duì)這三個(gè)參數(shù)的簡(jiǎn)單封裝荠割。Collectors工具類可通過靜態(tài)方法生成各種常用的Collector妹卿。
<R, A> R collect(Collector<? super T, A, R> collector);
舉個(gè)例子:
List list1 = Stream.of(1,2,3,4,5,6,7,8,9,10)
.collect(() -> new ArrayList<Integer>(),.//生成一個(gè)新的ArrayList實(shí)例
//接受兩個(gè)參數(shù),第一個(gè)是前面生成的ArrayList對(duì)象蔑鹦,
//二個(gè)是stream中包含的元素夺克,函數(shù)體就是把stream中的元素加入ArrayList對(duì)象中。
//此函數(shù)被反復(fù)調(diào)用直到原stream的元素被消費(fèi)完畢嚎朽;
(list, item) -> list.add(item),
//接受兩個(gè)參數(shù)铺纽,這兩個(gè)都是ArrayList類型的,函數(shù)體就是把第二個(gè)ArrayList全部加入到第一個(gè)中哟忍;
(lista, listb) -> lista.addAll(listb));
List list2 = Stream.of(1,2,3,4,5,6,7,8,9,10)
.collect(ArrayList::new,
ArrayList::add,
ArrayList::addAll);
List list3 = Stream.of(1,2,3,4,5,6,7,8,9,10)
.collect(Collectors.toList());
System.out.println(list3);
5. 總結(jié)
Stream的常用API基本介紹完畢狡门,應(yīng)該有了一個(gè)初步的認(rèn)識(shí)」埽總結(jié)一下Stream 的特性:
不是數(shù)據(jù)結(jié)構(gòu)其馏,它沒有內(nèi)部存儲(chǔ),它只是用操作管道從 source(數(shù)據(jù)結(jié)構(gòu)爆安、數(shù)組叛复、generator function、IO channel)抓取數(shù)據(jù)扔仓。它也絕不修改自己所封裝的底層數(shù)據(jù)結(jié)構(gòu)的數(shù)據(jù)褐奥。例如 Stream 的 filter 操作會(huì)產(chǎn)生一個(gè)不包含被過濾元素的新 Stream,而不是從 source 刪除那些元素翘簇。所以也不支持索引訪問撬码。
所有 Stream 的操作必須以 lambda 表達(dá)式為參數(shù)
惰性化,很多 Stream 操作是向后延遲的版保,一直到它弄清楚了最后需要多少數(shù)據(jù)才會(huì)開始呜笑。
Intermediate 操作永遠(yuǎn)是惰性化的。并行能力找筝,當(dāng)一個(gè) Stream 是并行化的蹈垢,就不需要再寫多線程代碼,所有對(duì)它的操作會(huì)自動(dòng)并行進(jìn)行的袖裕。
可以是無(wú)限的曹抬,集合有固定大小,Stream 則不必急鳄。limit(n) 和 findFirst() 這類的 short-circuiting 操作可以對(duì)無(wú)限的 Stream 進(jìn)行運(yùn)算并很快完成谤民。