????Jav8中,在核心類(lèi)庫(kù)中引入了新的概念,流(Stream)。流使得程序媛們得以站在更高的抽象層次上對(duì)集合進(jìn)行操作。
????今天,居士將主要介紹Steam類(lèi)中對(duì)應(yīng)集合上操作的幾個(gè)重要的方法。
1、 Steam舉例
????對(duì)使用Java的程序媛們,當(dāng)需要處理集合里的每一個(gè)數(shù)據(jù)時(shí),通常是使用迭代,再對(duì)每個(gè)返回的元素進(jìn)行處理。比如:
int count = 0;
ArrayList<String> nameList = new ArrayList<>();
nameList.add("仁昌居士");
nameList.add("仁昌居士");
nameList.add("痕無(wú)羽");
nameList.add("羽無(wú)痕");
for (String name: nameList) {
if(name.equals("仁昌居士"))
count++;
}
????盡管這段代碼思想上 并不難理解,但是存在幾個(gè)問(wèn)題:
????(1) 從代碼量上來(lái)看,每一次的循環(huán)集合類(lèi),都需要重復(fù)寫(xiě)很多的樣板代碼。
????(2)對(duì)于for循環(huán)寫(xiě)的代碼塊,有些程序媛可能很難理解其編寫(xiě)意圖。需要閱讀整個(gè)循環(huán)體后,才能有一定的理解。假設(shè)只有一個(gè)for循環(huán),相對(duì)理解并不難,但是當(dāng)出現(xiàn)多層嵌套循環(huán),那理解所花費(fèi)的成本就大幅度提升了。
????分析一下for循環(huán)的實(shí)現(xiàn)原理,可知是通過(guò)調(diào)用了iterator()方法,產(chǎn)生了一個(gè)Iterator對(duì)象,通過(guò)while方法遍歷的顯式調(diào)用這個(gè)對(duì)象的hasNext()和next()方法。以實(shí)現(xiàn)需求。這種遍歷過(guò)程叫做外部遍歷,是一種串行化操作。
Iterator<String> iterator = nameList.iterator();
while(iterator.hasNext()){
String name = iterator.next();
if(name.equals("仁昌居士")){
count++;
}
}
????注意事項(xiàng):為什么對(duì)for循環(huán)叫他外部遍歷而不是外部迭代的原因?可見(jiàn)另一篇文章:還未寫(xiě),周末寫(xiě)。
????相對(duì)于外部遍歷,還有一種方法叫做內(nèi)部遍歷。通過(guò)內(nèi)部遍歷,將上述代碼實(shí)現(xiàn)為:
long count = nameList.stream().filter(name -> name.equals("仁昌居士"))
.count();
????上述代碼實(shí)際是三步,第一步:nameList創(chuàng)建了一個(gè)Stream實(shí)例,第二步:用fliter操作符過(guò)濾找出為“仁昌居士”的name,并轉(zhuǎn)換成另外一個(gè)Stream,第三步:把Stream的里面包含的內(nèi)容按照某種算法來(lái)成型成一個(gè)值,代碼中式用count操作符計(jì)算有幾個(gè)這樣的name。
2、 惰性求值和及早求值
????通常,在Java中調(diào)有一個(gè)方法,計(jì)算機(jī)會(huì)隨機(jī)執(zhí)行相應(yīng)的操作,比如通過(guò)println在終端上輸出一條信息。Stream里的方法則有些不同。比如說(shuō):
nameList.stream().filter(name -> name.equals("仁昌居士"));
????這行代碼并沒(méi)有通過(guò)fliter得到新的集合,只是對(duì)Stream進(jìn)行了描述,這種方法叫做“惰性求值”方法,而之后的“.count()”使Stream產(chǎn)生了值的方法,叫做“及早求值”方法。
????最好的驗(yàn)證方式就如下。
????單純的在filter中加入一條println語(yǔ)句:
nameList.stream()
.filter(name -> {
System.out.println(name);
return name.equals("仁昌居士");
});
???? 運(yùn)行結(jié)果是程序并沒(méi)有輸出對(duì)應(yīng)信息。
????再測(cè)試:在后面加入一個(gè)及早求值方法,如count(),將會(huì)得到輸出結(jié)果。
nameList.stream()
.filter(name -> {
System.out.println(name);
return name.equals("仁昌居士");
})
.count();
????想知道操作符是惰性求值操作符還是及早求值操作符,只需觀察其返回值,如果返回值是Stream,則是惰性求值操作符;如果返回值是另一個(gè)類(lèi)型或者是void,則是及早求值操作符。通過(guò)這種多個(gè)惰性求值操作符+一個(gè)及早求值操作符消費(fèi)為結(jié)尾的鏈來(lái)得到想要的值,這個(gè)過(guò)程和建造者Builder模式很相似。建造者Builder模式就是通過(guò)使用一系列操作設(shè)置屬性和配置,最后通過(guò)一個(gè)build方法,將對(duì)象真正創(chuàng)建出來(lái)。
3、 常用的Stream操作符
????現(xiàn)在講述幾個(gè)比較常用的Stream API。
3.1 創(chuàng)建Stream操作符
3.1.1 of
????Stream的of操作符,是將一組數(shù)據(jù)生成一個(gè)Stream。是一個(gè)惰性求值操作符。
Stream nameStream = Stream.of("仁昌居士","痕無(wú)羽","羽無(wú)痕");
3.1.2 generate
????生成一個(gè)無(wú)限長(zhǎng)度的Stream,其元素的生成是通過(guò)給定的Supplier(這個(gè)接口可以看成一個(gè)對(duì)象的工廠,每次調(diào)用返回一個(gè)給定類(lèi)型的對(duì)象),也是一個(gè)惰性求值操作符。
Stream.generate(() -> Math.random());
????生成一個(gè)無(wú)限長(zhǎng)度的Stream,其中值是隨機(jī)的。這個(gè)無(wú)限長(zhǎng)度Stream是懶加載,一般這種無(wú)限長(zhǎng)度的Stream都會(huì)配合Stream的limit()方法來(lái)用。
3.1.3 iterate
????iterate操作符生成無(wú)限長(zhǎng)度的Stream,和generator不同的是,其元素的生成是重復(fù)對(duì)給定的種子值(seed)調(diào)用用戶(hù)指定函數(shù)來(lái)生成的。其中包含的元素可以認(rèn)為是:seed,f(seed),f(f(seed))無(wú)限循環(huán),也是惰性求值操作符
Stream.iterate(1, item -> item + 1).limit(10).forEach(System.out::println);
????這段代碼就是先獲取一個(gè)無(wú)限長(zhǎng)度的正整數(shù)集合的Stream,然后取出前10個(gè)打印。千萬(wàn)注意:使用limit方法,不然會(huì)無(wú)限打印下去。
3.2 轉(zhuǎn)換Stream操作符
3.2.1 map
????map操作符的作用就是將Stream中的每個(gè)值進(jìn)行同一個(gè)操作的處理后,再將其轉(zhuǎn)換為一個(gè)新的Stream,所以是惰性求值操作符。
List<String> list = Stream.of(1,2,3)
.map(integer -> String.valueOf(integer))
.collect(Collectors.toList());
????看上面這段代碼可知,通過(guò)map操作符和Lambda表達(dá)式將一個(gè)Integer類(lèi)型的參數(shù)轉(zhuǎn)成了一個(gè)String的返回值。參數(shù)和返回值直接不必是同一種類(lèi)型,但是Lambda表達(dá)式,必須是Function接口(只包含一個(gè)參數(shù)的普通函數(shù)接口)的一個(gè)實(shí)例。
????注意事項(xiàng):用map操作符得到的還是Stream。
3.2.2 flatMap
????flatMap操作符不同于map操作符將Stream中的值轉(zhuǎn)換為新值,他能將多個(gè)Stream合成一個(gè)Stream,返回值也是Stream。是惰性求值操作符。
ArrayList<Integer> arrayList1 = new ArrayList<>();
arrayList1.add(1);
arrayList1.add(2);
ArrayList<Integer> arrayList2 = new ArrayList<>();
arrayList2.add(3);
arrayList2.add(4);
List<Integer> list3 = Stream.of(arrayList1,arrayList2)
.flatMap(numbers -> numbers.stream())
.collect(Collectors.toList());
????通過(guò)stream()方法,將每個(gè)ArrayList轉(zhuǎn)換成了Stream對(duì)象,其余部分由flatMap操作符處理,得到的Stream是Stream.of(1,2,3,4)。
3.2.3 distinct
????distinct操作符,是對(duì)Stream中包含的元素進(jìn)行去重操作(去重邏輯依賴(lài)元素的equals方法),新生成的Stream中沒(méi)有重復(fù)的元素。是惰性求值操作符。
Stream stream = Stream.of(1, 2, 3, 4,1,2,2,3,4)
.distinct();
????得到的Stream里面的元素只有1,2,3,4四個(gè)。重復(fù)的都被去掉了。
3.2.4 filter
????fliter操作符,上文已經(jīng)提及過(guò)了,是用于過(guò)濾的惰性求值操作符。
List<Integer> list = Stream.of(1,2,3)
.filter(integer -> integer >1)
.collect(Collectors.toList());
????和map操作符相似,filter操作符接受一個(gè)函數(shù)為參數(shù),該函數(shù)通過(guò)Lambda表達(dá)式表示,如這段代碼,Lambda表達(dá)式將會(huì)對(duì)大于1的返回true,否則返回false。這段代碼就是通過(guò)filter操作符過(guò)濾選擇Lambda表達(dá)式返回值為true的元素保留生成新的Stream,并通過(guò)collect操作符得到符合要求的List。
3.2.5 peek
????peek生成一個(gè)包含原Stream的所有元素的新Stream,同時(shí)會(huì)提供一個(gè)消費(fèi)函數(shù)(Consumer實(shí)例),當(dāng)最終用及早求值操作符消費(fèi)此Stream時(shí),新Stream每個(gè)元素都會(huì)執(zhí)行給定的消費(fèi)函數(shù)。是惰性求值操作符。
nameList.stream()
.filter(name -> name.equals("仁昌居士"))
.peek(name -> System.out.println(name))
.collect(Collectors.toList());
3.2.6 limit
????limit對(duì)一個(gè)Stream進(jìn)行截?cái)嗖僮鳎@取其前N個(gè)元素,如果原Stream中包含的元素個(gè)數(shù)小于N,那就獲取其所有的元素,是惰性求值操作符。
Stream stream = Stream.of(1, 2, 3, 4,5,6,7,8,9,10)
.limit(3);
????得到的新的Stream的元素只有前3個(gè)。后面的被截?cái)嗔恕?/p>
3.2.7 skip
????返回一個(gè)跳過(guò)原Stream的前N個(gè)元素后剩下元素組成的新Stream,如果原Stream中包含的元素個(gè)數(shù)小于N,那么返回空Stream,是惰性求值操作符。
Stream stream = Stream.of(1, 2, 3, 4,5,6,7,8,9,10)
.skip(3);
????得到的新的Stream的元素只有后7個(gè)。前面的3個(gè)被跳過(guò)不要了。
3.3 成型(Reduce)Stream操作符
???? 成型(Reduce)Stream操作符和.reduce()操作符是兩個(gè)東西。
????成型(Reduce)是個(gè)概念,我將其理解為將Stream在經(jīng)過(guò)多次轉(zhuǎn)換操作后確定最終成型得到一個(gè)特定的非Stream的結(jié)果。
????而成型(Reduce)Stream操作符就是對(duì)Stream反復(fù)使用某個(gè)合并操作,把序列中的元素合并成一個(gè)整合結(jié)果的操作符。比如:max操作符、min操作符、sum操作符、count操作符、reduce()操作符、collect操作符等等。
????注意事項(xiàng):其中collect操作符和其他幾個(gè)操作符不同。他最終成型的結(jié)果是一個(gè)可變的容器,比如Collection或者StringBuilder。
3.3.1 max和min
????Stream中進(jìn)行大小比較是比較常用的操作,所以有了max和min操作符,返回值類(lèi)型是Optional,這是Java8防止出現(xiàn)NPE的一種可行方法,后面的文章會(huì)詳細(xì)介紹,這里就簡(jiǎn)單的認(rèn)為是一個(gè)容器,其中可能會(huì)包含0個(gè)或者1個(gè)對(duì)象。。
????查找Stream中的最大或最小元素,就要考慮是用什么作為排序的指標(biāo)。
Integer integer3 = Stream.of(1, 2, 3, 4)
.min((x,y) -> x.compareTo(y))
.get();
????通過(guò)比較兩個(gè)對(duì)象的值的大小,來(lái)得到最小值。對(duì)于這個(gè)指標(biāo),也可以通過(guò)Comparator對(duì)象。
Integer integer = Stream.of(1, 2, 3, 4)
.min(Comparator.naturalOrder())
.get();
????max和min方法同理,意思也一目了然,所以不用過(guò)多描述,都是及早求值操作符。
3.3.2 sum
????sum操作符不是所有的Stream對(duì)象都有的,只有IntStream、LongStream和DoubleStream是實(shí)例才有。
int sum = IntStream.of(1, 2, 3, 4,5,6,7,8,9,10)
.sum ();
????sum為55。求和的及早求值操作符。
3.3.3 count
????count操作符不是求Stream中元素的數(shù)量。
long count= Stream.of(0,1, 2, 3, 4,5,6,7,8,9)
.count();
????count為10。求元素個(gè)數(shù)的及早求值操作符。
3.3.4 reduce
????reduce操作符是及早求值操作符,接受一個(gè)元素序列為輸入,反復(fù)使用某個(gè)合并操作,把序列中的元素合并成一個(gè)匯總的結(jié)果,其生成的值不是隨意的,而是根據(jù)指定的計(jì)算模型。像之前的count、min、max操作符都是reduce操作。
????reduce方法有三個(gè)override的方法。
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);
???? 先來(lái)看reduce方法的第一種形式,其方法定義如下:
Optional<T> reduce(BinaryOperator<T> accumulator);
????接受一個(gè)BinaryOperator類(lèi)型的參數(shù),在使用的時(shí)候我們可以用lambda表達(dá)式來(lái)。
Stream.of(1,2,3,4,5,6,7,8,9,10).reduce((sum, item) -> sum + item).get();
????結(jié)果都為55。
???? 可以看到reduce方法接受一個(gè)函數(shù),這個(gè)函數(shù)有兩個(gè)參數(shù),第一個(gè)參數(shù)是上次函數(shù)執(zhí)行的返回值(也稱(chēng)為中間結(jié)果),第二個(gè)參數(shù)是stream中的元素,這個(gè)函數(shù)把這兩個(gè)值相加,得到的和會(huì)被賦值給下次執(zhí)行這個(gè)函數(shù)的第一個(gè)參數(shù)。要注意的是:第一次執(zhí)行的時(shí)候第一個(gè)參數(shù)的值是Stream的第一個(gè)元素,第二個(gè)參數(shù)是Stream的第二個(gè)元素。這個(gè)方法返回值類(lèi)型是Optional。
????再來(lái)看reduce方法的第二種形式,其方法定義如下:
T reduce(T identity, BinaryOperator<T> accumulator);
????與第一種變形相同的是都會(huì)接受一個(gè)BinaryOperator函數(shù)接口,不同的是其會(huì)接受一個(gè)identity參數(shù),用來(lái)指定Stream循環(huán)的初始值。如果Stream為空,就直接返回該值。另一方面,該方法不會(huì)返回Optional,因?yàn)樵摲椒ú粫?huì)出現(xiàn)null。
Stream.of(1,2,3,4,5,6,7,8,9,10).reduce(1, (sum, item) -> sum + item)).get();
????結(jié)果都為56。
????變形1,未定義初始值,從而第一次執(zhí)行的時(shí)候第一個(gè)參數(shù)的值是Stream的第一個(gè)元素,第二個(gè)參數(shù)是Stream的第二個(gè)元素。
???? 變形2,定義了初始值,從而第一次執(zhí)行的時(shí)候第一個(gè)參數(shù)的值是初始值,第二個(gè)參數(shù)是Stream的第一個(gè)元素。
<U> U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator<U> combiner);
????對(duì)于第三種變形,我們先看各個(gè)參數(shù)的含義,第一個(gè)參數(shù)類(lèi)型是實(shí)際返回實(shí)例的數(shù)據(jù)類(lèi)型,同時(shí)其為一個(gè)泛型也就是意味著該變形的可以返回任意類(lèi)型的數(shù)據(jù),第二個(gè)參數(shù)累加器accumulator,可以使用二元?表達(dá)式(即二元lambda表達(dá)式),聲明你在u上累加你的數(shù)據(jù)來(lái)源t的邏輯,例如(u,t)->u.sum(t),此時(shí)lambda表達(dá)式的行參列表是返回實(shí)例u和遍歷的集合元素t,函數(shù)體是在u上累加t,第三個(gè)參數(shù)組合器combiner,同樣是二元?表達(dá)式,(u,t)->u, 是用來(lái)處理并發(fā)操作的。因?yàn)镾tream是支持并發(fā)操作的,為了避免競(jìng)爭(zhēng),對(duì)于reduce線(xiàn)程都會(huì)有獨(dú)立的result,combiner的作用在于合并每個(gè)線(xiàn)程的result得到最終結(jié)果。這也說(shuō)明了了第三個(gè)函數(shù)參數(shù)的數(shù)據(jù)類(lèi)型必須為返回?cái)?shù)據(jù)類(lèi)型了。代碼并不好舉例,先不距離,在以后的講解中會(huì)提及。
3.3.5 collect
???? collect操作符:是一個(gè)及早求值操作符。它可以把Stream中的要有元素收集到一個(gè)結(jié)果容器中(比如Collection)。先看一下最通用的collect方法的定義(還有其他override方法)。
<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner);
????先來(lái)看看這三個(gè)參數(shù)的含義:Supplier supplier是一個(gè)工廠函數(shù),用來(lái)生成一個(gè)新的容器;BiConsumer accumulator也是一個(gè)函數(shù),用來(lái)把Stream中的元素添加到結(jié)果容器中;BiConsumer combiner還是一個(gè)函數(shù),用來(lái)把中間狀態(tài)的多個(gè)結(jié)果容器合并成為一個(gè)(并發(fā)的時(shí)候會(huì)用到)。
List<Integer> numsWithoutNull = Stream.of(1,2,3,4,5,6,7,8,9,10)
.collect(() -> new ArrayList<Integer>(),(list, item) -> list.add(item),(list1, list2) -> list1.addAll(list2));
????上面這段代碼就是把一個(gè)元素是Integer類(lèi)型的List收集到一個(gè)新的List中。進(jìn)一步看一下collect方法的三個(gè)參數(shù),都是lambda形式的函數(shù)。
???? 第一個(gè)函數(shù)生成一個(gè)新的ArrayList實(shí)例;
????第二個(gè)函數(shù)接受兩個(gè)參數(shù),第一個(gè)是前面生成的ArrayList對(duì)象,二個(gè)是stream中包含的元素,函數(shù)體就是把stream中的元素加入ArrayList對(duì)象中。第二個(gè)函數(shù)被反復(fù)調(diào)用直到原stream的元素被消費(fèi)完畢;
????第三個(gè)函數(shù)也是接受兩個(gè)參數(shù),這兩個(gè)都是ArrayList類(lèi)型的,函數(shù)體就是把多個(gè)ArrayList容器合并成為一個(gè)。
???? 但是上面的collect方法調(diào)用有些復(fù)雜了,有更簡(jiǎn)單的override方法,其依賴(lài)Collector。
<R, A> R collect(Collector<? super T, A, R> collector);
????進(jìn)一步,Java8還給我們提供了Collector的工具類(lèi)–Collectors,其中已經(jīng)定義了一些靜態(tài)工廠方法,比如:Collectors.toCollection()收集到Collection中, Collectors.toList()收集到List中和Collectors.toSet()收集到Set中,等等。這樣的靜態(tài)方法還有很多,這里就不一一介紹了,大家可以直接去看文檔。下面看看使用Collectors對(duì)于代碼的簡(jiǎn)化:
List<Integer> numsWithoutNull = Stream.of(1,2,3,4,5,6,7,8,9,10)
.collect(Collectors.toList());
????這段代碼將of()操作符得到的Stream,用collect(Collectors.toList())操作符從Stream中生成一個(gè)List。
4、 性能問(wèn)題
????完成了上述的講解,會(huì)發(fā)現(xiàn)在使用操作符時(shí),會(huì)出現(xiàn)對(duì)于一個(gè)Stream進(jìn)行多次轉(zhuǎn)換操作,每次都對(duì)Stream的每個(gè)元素進(jìn)行轉(zhuǎn)換,而且是執(zhí)行多次,這樣時(shí)間復(fù)雜度就是一個(gè)for循環(huán)里把所有操作都做掉的N(轉(zhuǎn)換的次數(shù))倍啊。其實(shí)不是這樣的,轉(zhuǎn)換操作都是lazy的,多個(gè)轉(zhuǎn)換操作只會(huì)在成型(Reduce)操作的時(shí)候融合起來(lái),一次循環(huán)完成。我們可以這樣簡(jiǎn)單的理解,Stream里有個(gè)操作函數(shù)的集合,每次轉(zhuǎn)換操作就是把轉(zhuǎn)換函數(shù)放入這個(gè)集合中,在成型(Reduce)操作的時(shí)候循環(huán)Stream對(duì)應(yīng)的集合,然后對(duì)每個(gè)元素一次性執(zhí)行所有的操作。
5、總結(jié)
????對(duì)于Stream,單純的書(shū)面理解是很難明白的,碼字看方法才是最好的學(xué)習(xí)方法。所以我的御姐兒,你還是多碼碼代碼吧。本居士很忙的啊。