Java8特(te)性(xing)詳解 lambda表達式(shi) Stream
1.lambda表達式
Java8最值得學習的(de)特性(xing)就(jiu)是Lambda表達(da)(da)式和Stream API,如果有(you)python或者javascript的(de)語(yu)言基礎,對理解Lambda表達(da)(da)式有(you)很(hen)大幫(bang)助,因(yin)為Java正在(zai)將(jiang)自己(ji)變(bian)的(de)更(geng)高(Sha)級(Gua),更(geng)人性(xing)化。--------可以這(zhe)么說lambda表達(da)(da)式其實就(jiu)是實現SAM接口的(de)語(yu)法糖。
lambda寫的好(hao)可(ke)以極大的減少代碼(ma)冗(rong)余(yu),同(tong)時可(ke)讀性也好(hao)過(guo)冗(rong)長的內部類(lei),匿名類(lei)。
先列(lie)舉兩個常(chang)見的(de)簡化(簡單(dan)的(de)代(dai)碼同樣(yang)好理解)
- 創建線程

- 排序

lambda表達(da)式配(pei)合Java8新特性(xing)Stream API可以將(jiang)業務功能通過函數式編程(cheng)簡(jian)潔的實(shi)現(xian)。(為后面的例子做鋪(pu)墊)
例如:

這段代碼就是對一(yi)個(ge)字(zi)符(fu)串(chuan)的(de)列(lie)表,把其中包含的(de)每個(ge)字(zi)符(fu)串(chuan)都轉換成全小寫的(de)字(zi)符(fu)串(chuan)。注意代碼第四(si)行的(de)map方(fang)(fang)法(fa)調用(yong),這里(li)map方(fang)(fang)法(fa)就是接受了一(yi)個(ge)lambda表達式(shi)。
1.1lambda表達式語法
1.1.1lambda表達式的一般語法
(Type1 param1, Type2 param2, ..., TypeN paramN) -> {
statment1;
statment2;
//.............
return statmentM;
}
這是(shi)lambda表(biao)達式的完全式語法,后(hou)面幾種語法是(shi)對它(ta)的簡(jian)化。
1.1.2單參數語法
param1 -> {
statment1;
statment2;
//.............
return statmentM;
}
當lambda表達式的參數個數只有一(yi)個,可以省(sheng)略小括(kuo)號
例如:將列表(biao)中的字符(fu)串轉換為全(quan)小寫
List<String> proNames = Arrays.asList(new String[]{"Ni","Hao","Lambda"});
List<String> lowercaseNames1 = proNames.stream().map(name -> {return name.toLowerCase();}).collect(Collectors.toList());
1.1.3單語句寫法
param1 -> statment
當lambda表達式只包含一條語句時,可以省略大括號、return和語句結尾的分號
例如:將(jiang)列表(biao)中(zhong)的字(zi)符(fu)串轉換為(wei)全小(xiao)寫
List<String> proNames = Arrays.asList(new String[]{"Ni","Hao","Lambda"});
List<String> lowercaseNames2 = proNames.stream().map(name -> name.toLowerCase()).collect(Collectors.toList());
1.1.4方法引用寫法
(方法引用和lambda一(yi)樣是(shi)Java8新(xin)語(yu)言特性,后面會講(jiang)到)
Class or instance :: method
例(li)如:將(jiang)列(lie)表中的字(zi)符(fu)串轉換為全(quan)小寫
List<String> proNames = Arrays.asList(new String[]{"Ni","Hao","Lambda"});
List<String> lowercaseNames3 = proNames.stream().map(String::toLowerCase).collect(Collectors.toList());
1.2lambda表達式可使用的變量
先舉例:
//將為列表中的字符串添加前綴字符串
String waibu = "lambda :";
List<String> proStrs = Arrays.asList(new String[]{"Ni","Hao","Lambda"});
List<String>execStrs = proStrs.stream().map(chuandi -> {
Long zidingyi = System.currentTimeMillis();
return waibu + chuandi + " -----:" + zidingyi;
}).collect(Collectors.toList());
execStrs.forEach(System.out::println);
輸出:
lambda :Ni -----:1474622341604
lambda :Hao -----:1474622341604
lambda :Lambda -----:1474622341604
變(bian)量waibu :外部變(bian)量
變量chuandi :傳遞變量
變量zidingyi :內部自定義變量
lambda表(biao)達(da)式(shi)可以(yi)訪問(wen)給它(ta)(ta)傳遞的(de)變(bian)量(liang),訪問(wen)自己內(nei)部定義的(de)變(bian)量(liang),同時(shi)也(ye)能訪問(wen)它(ta)(ta)外部的(de)變(bian)量(liang)。
不(bu)過lambda表達式訪問外部變(bian)量(liang)(liang)有一個非常重(zhong)要的限制:變(bian)量(liang)(liang)不(bu)可變(bian)(只是引(yin)用不(bu)可變(bian),而(er)不(bu)是真正的不(bu)可變(bian))。
當(dang)在表達式內部修改waibu = waibu + " ";時,IDE就會提(ti)示你(ni):
Local variable waibu defined in an enclosing scope must be final or effectively final
編(bian)譯(yi)時會報錯。因為(wei)變量(liang)waibu被lambda表(biao)達式引用,所(suo)以編(bian)譯(yi)器會隱式的把其當(dang)成final來處理(li)。
以(yi)前Java的(de)匿(ni)名內部類在(zai)訪問外部變量的(de)時候,外部變量必須用final修飾。現在(zai)java8對這(zhe)個限制做了優化(hua),可以(yi)不用顯示(shi)使用final修飾,但是編(bian)譯器隱式當成final來處理(li)。
1.3lambda表達式中的this概念
在(zai)lambda中,this不是指向lambda表(biao)達式(shi)產生的(de)那(nei)個SAM對象,而是聲(sheng)明它的(de)外部(bu)對象。
例如:
public class WhatThis {
public void whatThis(){
//轉全小寫
List<String> proStrs = Arrays.asList(new String[]{"Ni","Hao","Lambda"});
List<String> execStrs = proStrs.stream().map(str -> {
System.out.println(this.getClass().getName());
return str.toLowerCase();
}).collect(Collectors.toList());
execStrs.forEach(System.out::println);
}
public static void main(String[] args) {
WhatThis wt = new WhatThis();
wt.whatThis();
}
}
輸出:
com.wzg.test.WhatThis
com.wzg.test.WhatThis
com.wzg.test.WhatThis
ni
hao
lambda
2.方法引用和構造器引用
本人認為是(shi)進一步(bu)簡化lambda表達式(shi)的聲明(ming)的一種語法糖。
前面(mian)的例子(zi)中已有使用到(dao): execStrs.forEach(System.out::println);
2.1方法引用
objectName::instanceMethod
ClassName::staticMethod
ClassName::instanceMethod
前兩種(zhong)方(fang)式類似,等同于把lambda表達式的參(can)數直接當成instanceMethod|staticMethod的參(can)數來調用。比如(ru)System.out::println等同于x->System.out.println(x);Math::max等同于(x, y)->Math.max(x,y)。
最(zui)后一(yi)種方式,等同(tong)于把lambda表達(da)式的第一(yi)個(ge)參(can)(can)數當成instanceMethod的目標對(dui)象,其(qi)他剩余(yu)參(can)(can)數當成該方法的參(can)(can)數。比(bi)如String::toLowerCase等同(tong)于x->x.toLowerCase()。
可以這么理(li)解,前兩種是將傳(chuan)入(ru)對(dui)(dui)象當參數(shu)執(zhi)行方法,后(hou)一種是調用(yong)傳(chuan)入(ru)對(dui)(dui)象的方法。
2.2構造器引用
構造(zao)器引(yin)用語法如下:ClassName::new,把lambda表(biao)達式的參(can)數當成ClassName構造(zao)器的參(can)數 。例(li)如BigDecimal::new等(deng)同于x->new BigDecimal(x)。
3.Stream語法
兩句話理解Stream:
1.Stream是(shi)元素的集合(he),這點讓Stream看起來用(yong)些類(lei)似Iterator;
2.可以支持順序和(he)并行的對原Stream進行匯聚的操作(zuo);
大(da)家可以把(ba)Stream當成一(yi)個裝飾(shi)后的(de)(de)(de)Iterator。原始(shi)版本(ben)的(de)(de)(de)Iterator,用(yong)戶只(zhi)能逐(zhu)個遍歷元素(su)并(bing)對其執行某些操(cao)作(zuo);包裝后的(de)(de)(de)Stream,用(yong)戶只(zhi)要給出需要對其包含(han)的(de)(de)(de)元素(su)執行什么(me)(me)操(cao)作(zuo),比(bi)如“過(guo)濾掉長度大(da)于(yu)10的(de)(de)(de)字符串”、“獲(huo)取每(mei)個字符串的(de)(de)(de)首字母(mu)”等,具體這些操(cao)作(zuo)如何應用(yong)到每(mei)個元素(su)上,就(jiu)給Stream就(jiu)好了!原先是人告(gao)(gao)訴計(ji)(ji)算(suan)機(ji)一(yi)步(bu)一(yi)步(bu)怎(zen)么(me)(me)做(zuo),現在是告(gao)(gao)訴計(ji)(ji)算(suan)機(ji)做(zuo)什么(me)(me),計(ji)(ji)算(suan)機(ji)自(zi)己決定怎(zen)么(me)(me)做(zuo)。當然這個“怎(zen)么(me)(me)做(zuo)”還是比(bi)較(jiao)弱的(de)(de)(de)。
例子:
//Lists是Guava中的一個工具類
List<Integer> nums = Lists.newArrayList(1,null,3,4,null,6);
nums.stream().filter(num -> num != null).count();
上(shang)面這段代碼是(shi)獲(huo)取一(yi)個(ge)(ge)List中,元(yuan)素不為null的(de)個(ge)(ge)數。這段代碼雖(sui)然很簡(jian)短(duan),但(dan)是(shi)卻(que)是(shi)一(yi)個(ge)(ge)很好的(de)入門級別的(de)例子來體(ti)現(xian)如何(he)使用Stream,正所謂“麻(ma)雀(que)雖(sui)小五臟俱全(quan)”。我(wo)們現(xian)在開始深入解(jie)刨(bao)這個(ge)(ge)例子,完(wan)成以后你可能可以基本(ben)掌握Stream的(de)用法(fa)!

圖(tu)片(pian)就是(shi)對于Stream例子(zi)的(de)(de)(de)一(yi)個(ge)(ge)解(jie)析,可(ke)以很(hen)清楚的(de)(de)(de)看見:原本一(yi)條語句被三種顏色的(de)(de)(de)框分(fen)割成了三個(ge)(ge)部分(fen)。紅(hong)色框中(zhong)的(de)(de)(de)語句是(shi)一(yi)個(ge)(ge)Stream的(de)(de)(de)生(sheng)命(ming)開始的(de)(de)(de)地方(fang)(fang),負責創建(jian)一(yi)個(ge)(ge)Stream實例;綠色框中(zhong)的(de)(de)(de)語句是(shi)賦予(yu)Stream靈魂(hun)的(de)(de)(de)地方(fang)(fang),把(ba)一(yi)個(ge)(ge)Stream轉換成另外一(yi)個(ge)(ge)Stream,紅(hong)框的(de)(de)(de)語句生(sheng)成的(de)(de)(de)是(shi)一(yi)個(ge)(ge)包含所(suo)有nums變量的(de)(de)(de)Stream,進過綠框的(de)(de)(de)filter方(fang)(fang)法以后,重新生(sheng)成了一(yi)個(ge)(ge)過濾掉原nums列表所(suo)有null以后的(de)(de)(de)Stream;藍色框中(zhong)的(de)(de)(de)語句是(shi)豐(feng)收的(de)(de)(de)地方(fang)(fang),把(ba)Stream的(de)(de)(de)里面包含的(de)(de)(de)內(nei)容按照某(mou)種算法來(lai)匯聚成一(yi)個(ge)(ge)值(zhi),例子(zi)中(zhong)是(shi)獲取(qu)Stream中(zhong)包含的(de)(de)(de)元素個(ge)(ge)數。如果這樣解(jie)析以后,還不(bu)理(li)解(jie),那就只能動用“核武器”–圖(tu)形(xing)化,一(yi)圖(tu)抵千(qian)言(yan)!

使用(yong)Stream的(de)基本步驟:
1.創(chuang)建Stream;
2.轉換Stream,每次(ci)轉換原有(you)Stream對象(xiang)(xiang)不改變,返回一個新的Stream對象(xiang)(xiang)(**可(ke)以有(you)多次(ci)轉換**);
3.對Stream進(jin)行聚合(Reduce)操(cao)作,獲取想(xiang)要的結果;
3.1怎么得到Stream
最常用的(de)創建Stream有兩種途徑:
1.通過Stream接口的靜態工廠方法(注意:Java8里接口可以帶靜態方法);
2.通過Collection接口的(de)默認(ren)方(fang)(fang)法(默認(ren)方(fang)(fang)法:Default method,也是(shi)Java8中(zhong)的(de)一個(ge)新特性,就是(shi)接口中(zhong)的(de)一個(ge)帶有實現的(de)方(fang)(fang)法)–stream(),把(ba)一個(ge)Collection對(dui)象轉(zhuan)換成(cheng)Stream
3.1.1 使用Stream靜態方法來創建Stream
1. of方(fang)法:有(you)兩個(ge)overload方(fang)法,一(yi)個(ge)接(jie)受變長參數,一(yi)個(ge)接(jie)口單一(yi)值
Stream<Integer> integerStream = Stream.of(1, 2, 3, 5);
Stream<String> stringStream = Stream.of("taobao");
2. generator方法:生(sheng)(sheng)成一個無限長度的(de)Stream,其(qi)元素的(de)生(sheng)(sheng)成是通(tong)過(guo)給(gei)定(ding)的(de)Supplier(這個接口(kou)可以看(kan)成一個對象的(de)工廠,每次調用(yong)返回一個給(gei)定(ding)類型(xing)的(de)對象)
Stream.generate(new Supplier<Double>() {
@Override
public Double get() {
return Math.random();
}
});
Stream.generate(() -> Math.random());
Stream.generate(Math::random);
三條語(yu)句的(de)(de)作用都是(shi)(shi)(shi)一樣的(de)(de),只是(shi)(shi)(shi)使用了lambda表(biao)達式(shi)和(he)方法引(yin)用的(de)(de)語(yu)法來(lai)(lai)簡化代碼。每條語(yu)句其實都是(shi)(shi)(shi)生(sheng)成一個無(wu)限(xian)長度的(de)(de)Stream,其中(zhong)值(zhi)是(shi)(shi)(shi)隨機的(de)(de)。這(zhe)個無(wu)限(xian)長度Stream是(shi)(shi)(shi)懶加載,一般(ban)這(zhe)種無(wu)限(xian)長度的(de)(de)Stream都會配合Stream的(de)(de)limit()方法來(lai)(lai)用。
3. iterate方法:也(ye)是生(sheng)成(cheng)無限(xian)長度的Stream,和generator不(bu)同(tong)的是,其元(yuan)素的生(sheng)成(cheng)是重(zhong)復對給定的種子值(seed)調用用戶指定函數(shu)來生(sheng)成(cheng)的。其中包含的元(yuan)素可(ke)以認為是:seed,f(seed),f(f(seed))無限(xian)循環
Stream.iterate(1, item -> item + 1).limit(10).forEach(System.out::println);
這段代碼就是先獲取(qu)一個(ge)無(wu)(wu)限長(chang)度的正(zheng)整數(shu)集合的Stream,然(ran)后取(qu)出(chu)前(qian)10個(ge)打(da)印(yin)。千萬記住使用(yong)limit方法,不然(ran)會(hui)無(wu)(wu)限打(da)印(yin)下去。
3.1.2通過Collection子類獲取Stream
Collection接口有(you)一(yi)個stream方法,所以其所有(you)子(zi)類(lei)都(dou)都(dou)可以獲取對應(ying)的Stream對象。
public interface Collection<E> extends Iterable<E> {
//其他方法省略
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
}
3.2轉換Stream
轉換Stream其實就是把一個Stream通過某些行為轉換成一個新的Stream。Stream接口中定義了幾個常用的轉換方法,下面我們挑選幾個常用的轉換方法來解釋。
1. distinct: 對于Stream中包含(han)的元素進(jin)行去重操作(去重(zhong)邏輯(ji)依賴元素的(de)equals方法),新生成的(de)Stream中沒有重(zhong)復的(de)元素;

2. filter: 對于Stream中包含的元(yuan)素使用給定的過濾(lv)函數進(jin)行過濾(lv)操(cao)作,新生成的(de)Stream只(zhi)包含符合條件(jian)的(de)元素;

3. map: 對于Stream中包含的元素(su)使用給(gei)定的轉換函數進行轉換操作,新(xin)生(sheng)(sheng)成(cheng)的(de)Stream只包含轉(zhuan)換(huan)生(sheng)(sheng)成(cheng)的(de)元素。這個方(fang)(fang)法(fa)有三個對于原始類(lei)型(xing)的(de)變(bian)種方(fang)(fang)法(fa),分(fen)別是(shi):mapToInt,mapToLong和mapToDouble。這三個方(fang)(fang)法(fa)也比較好理解(jie),比如(ru)mapToInt就是(shi)把原始Stream轉(zhuan)換(huan)成(cheng)一個新(xin)的(de)Stream,這個新(xin)生(sheng)(sheng)成(cheng)的(de)Stream中的(de)元素都是(shi)int類(lei)型(xing)。之所以(yi)會有這樣三個變(bian)種方(fang)(fang)法(fa),可以(yi)免除自動裝箱(xiang)/拆箱(xiang)的(de)額外消耗;

4. flatMap:和(he)map類似,不(bu)同(tong)的是(shi)其每個元素(su)轉(zhuan)換得(de)到(dao)(dao)的是(shi)Stream對象,會把子Stream中(zhong)的元素(su)壓縮(suo)到(dao)(dao)父集合(he)中(zhong);

flatMap給一段代碼理解(jie):
Stream<List<Integer>> inputStream = Stream.of( Arrays.asList(1), Arrays.asList(2, 3), Arrays.asList(4, 5, 6) ); Stream<Integer> outputStream = inputStream. flatMap((childList) -> childList.stream());
flatMap 把 input Stream 中的層(ceng)級(ji)結構扁(bian)平化,就是將(jiang)最底(di)層(ceng)元(yuan)素抽出來放到一(yi)起,最終 output 的新 Stream 里面已經沒有 List 了,都是直接(jie)的數字。
5. peek: 生(sheng)成一個(ge)包含原Stream的(de)所有元素的(de)新Stream,同時(shi)會提供一個(ge)消費函(han)數(Consumer實例),新Stream每個(ge)元素被消費的(de)時(shi)候都會執行給(gei)定的(de)消費函(han)數;

6. limit: 對一個(ge)Stream進行截斷操作(zuo),獲取(qu)其(qi)(qi)前N個(ge)元(yuan)素(su),如果(guo)原Stream中包(bao)含(han)的(de)元(yuan)素(su)個(ge)數小于N,那就獲取(qu)其(qi)(qi)所有(you)的(de)元(yuan)素(su);

7. skip: 返回一個(ge)(ge)丟棄原Stream的前N個(ge)(ge)元(yuan)(yuan)素(su)后剩下(xia)元(yuan)(yuan)素(su)組成的新Stream,如果原Stream中包含的元(yuan)(yuan)素(su)個(ge)(ge)數小(xiao)于N,那(nei)么返回空Stream;

整體調用例子:
List<Integer> nums = Lists.newArrayList(1,1,null,2,3,4,null,5,6,7,8,9,10);
System.out.println(“sum is:”+nums.stream().filter(num -> num != null).distinct().mapToInt(num -> num * 2).peek(System.out::println).skip(2).limit(4).sum());
這(zhe)段(duan)代(dai)碼(ma)演示了(le)上面介紹的所有轉(zhuan)換(huan)方法(fa)(除了(le)flatMap),簡單解(jie)釋一下這(zhe)段(duan)代(dai)碼(ma)的含義:給定一個Integer類型的List,獲取(qu)其對應的Stream對象,然后(hou)進(jin)(jin)行(xing)過(guo)濾掉(diao)null,再(zai)去(qu)重(zhong),再(zai)每個元(yuan)素(su)乘以(yi)2,再(zai)每個元(yuan)素(su)被(bei)消費(fei)的時候打(da)印自(zi)身,在跳過(guo)前(qian)兩個元(yuan)素(su),最后(hou)去(qu)前(qian)四個元(yuan)素(su)進(jin)(jin)行(xing)加和運算(解(jie)釋一大堆(dui),很像廢話,因為基(ji)本看了(le)方法(fa)名就知道要(yao)做什(shen)么(me)了(le)。這(zhe)個就是聲明式(shi)編程的一大好(hao)處!)。大家(jia)可以(yi)參考上面對于每個方法(fa)的解(jie)釋,看看最終的輸(shu)出(chu)是什(shen)么(me)。
2
4
6
8
10
12
sum is:36
可能會有這樣的疑問:在對于一個Stream進行多次轉換操作,每次都對Stream的每個元素進行轉換,而且是執行多次,這樣時間復雜度就是一個for循環里把所有操作都做掉的N(轉換的次數)倍啊。其實不是這樣的,轉(zhuan)換(huan)操(cao)(cao)作(zuo)(zuo)都是lazy的(de),多個(ge)(ge)轉(zhuan)換(huan)操(cao)(cao)作(zuo)(zuo)只會在(zai)匯(hui)聚操(cao)(cao)作(zuo)(zuo)(見下節)的(de)時(shi)候融合起來(lai),一次(ci)循(xun)環(huan)完成。我(wo)們可以這(zhe)樣簡單(dan)的(de)理(li)解,Stream里有個(ge)(ge)操(cao)(cao)作(zuo)(zuo)函(han)數(shu)的(de)集(ji)合,每次(ci)轉(zhuan)換(huan)操(cao)(cao)作(zuo)(zuo)就是把(ba)轉(zhuan)換(huan)函(han)數(shu)放入這(zhe)個(ge)(ge)集(ji)合中(zhong),在(zai)匯(hui)聚操(cao)(cao)作(zuo)(zuo)的(de)時(shi)候循(xun)環(huan)Stream對應的(de)集(ji)合,然后對每個(ge)(ge)元素執(zhi)行所(suo)有的(de)函(han)數(shu)。
3.3匯聚(Reduce)Stream
匯聚操作(也稱為折疊)接受一個元素序列為輸入,反復使用某個合并操作,把序列中的元素合并成一個匯總的結果。比如查找一個數字列表的總和或者最大值,或者把這些數字累積成一個List對象。Stream接口有一些通用的(de)匯聚操作,比如(ru)reduce()和collect();也有一些特定用途的(de)匯聚操作,比如(ru)sum(),max()和count()。注意(yi):sum方(fang)法不是(shi)(shi)所有的Stream對象(xiang)都有的,只(zhi)有IntStream、LongStream和(he)DoubleStream是(shi)(shi)實例才有。
下面會分(fen)兩部分(fen)來介紹匯(hui)聚操(cao)作:
可變匯聚:把輸入的元素們累積到一個可變的容器中,比如Collection或者StringBuilder;
其他匯聚:除去可(ke)變(bian)匯聚剩下(xia)的(de),一(yi)般都不是(shi)通過反復(fu)修改(gai)某個可(ke)變(bian)對(dui)象,而是(shi)通過把前一(yi)次的(de)匯聚結果當成(cheng)下(xia)一(yi)次的(de)入參,反復(fu)如(ru)(ru)此。比如(ru)(ru)reduce,count,allMatch;
3.3.1可變匯聚
可變匯聚對應的只有一個方法:collect,正如(ru)其名字顯示(shi)的,它(ta)可(ke)以(yi)把Stream中的要有(you)元(yuan)素收(shou)集到(dao)一個結果容器中(比如(ru)Collection)。先看一下最通用(yong)的collect方(fang)法(fa)的定義(還有其他override方(fang)法(fa)):
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
先(xian)來(lai)看(kan)(kan)看(kan)(kan)這三個(ge)(ge)參數(shu)(shu)的含義:Supplier supplier是一(yi)個(ge)(ge)工廠函數(shu)(shu),用(yong)來(lai)生成一(yi)個(ge)(ge)新(xin)的容器(qi);BiConsumer accumulator也(ye)是一(yi)個(ge)(ge)函數(shu)(shu),用(yong)來(lai)把(ba)Stream中的元素添加到結果(guo)容器(qi)中;BiConsumer combiner還是一(yi)個(ge)(ge)函數(shu)(shu),用(yong)來(lai)把(ba)中間(jian)狀態的多個(ge)(ge)結果(guo)容器(qi)合并(bing)成為一(yi)個(ge)(ge)(并(bing)發(fa)的時(shi)候(hou)會用(yong)到)。看(kan)(kan)暈(yun)了?來(lai)段代碼!
List<Integer> nums = Lists.newArrayList(1,1,null,2,3,4,null,5,6,7,8,9,10);
List<Integer> numsWithoutNull = nums.stream().filter(num -> num != null).
collect(() -> new ArrayList<Integer>(),
(list, item) -> list.add(item),
(list1, list2) -> list1.addAll(list2));
上面這(zhe)段(duan)代碼(ma)就(jiu)是(shi)對一(yi)(yi)個(ge)元素是(shi)Integer類型的(de)(de)List,先過濾掉全部的(de)(de)null,然后把剩下(xia)的(de)(de)元素收集到(dao)一(yi)(yi)個(ge)新的(de)(de)List中。進(jin)一(yi)(yi)步看一(yi)(yi)下(xia)collect方法(fa)的(de)(de)三(san)個(ge)參數(shu),都(dou)是(shi)lambda形式的(de)(de)函數(shu)。
第一個函數生成一個新的ArrayList實例;
第二個函數接受兩個參數,第一個是前面生成的ArrayList對象,二個是stream中包含的元素,函數體就是把stream中的元素加入ArrayList對象中。第二個函數被反復調用直到原stream的元素被消費完畢;
第三個函數也是接受兩個參數,這兩個都是ArrayList類型的,函數體就是把第二個ArrayList全部加入到第一個中;
但(dan)是(shi)上面的collect方(fang)法(fa)(fa)調用(yong)也有點太復雜了,沒關系!我們來(lai)看一(yi)下(xia)collect方(fang)法(fa)(fa)另外(wai)一(yi)個override的版本(ben),其依賴[Collector]()。
<R, A> R collect(Collector<? super T, A, R> collector);
這樣清爽多了(le)!Java8還(huan)給我們提供了(le)Collector的工具類–[Collectors](),其中已經定義(yi)了(le)一些(xie)靜態工廠方法,比如:Collectors.toCollection()收集(ji)到(dao)Collection中, Collectors.toList()收集(ji)到(dao)List中和Collectors.toSet()收集(ji)到(dao)Set中。這樣的靜態方法還(huan)有很多,這里就不一一介紹了(le),大家可(ke)以直接(jie)去看JavaDoc。下面(mian)看看使用(yong)Collectors對于代(dai)碼的簡化:
List<Integer> numsWithoutNull = nums.stream().filter(num -> num != null).
collect(Collectors.toList());
3.3.2其他匯聚
– reduce方(fang)法:reduce方(fang)法非常(chang)(chang)的(de)通用,后面介(jie)紹(shao)的(de)count,sum等(deng)都(dou)可以(yi)使(shi)用其實現(xian)。reduce方(fang)法有三個override的(de)方(fang)法,本文介(jie)紹(shao)兩個最常(chang)(chang)用的(de)。先來(lai)看reduce方(fang)法的(de)第一種形(xing)式,其方(fang)法定義如下(xia):
Optional<T> reduce(BinaryOperator<T> accumulator);
接受一個BinaryOperator類型的(de)參數,在使用(yong)的(de)時候我們可(ke)以用(yong)lambda表達式來。
List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10);
System.out.println("ints sum is:" + ints.stream().reduce((sum, item) -> sum + item).get());
可以看到reduce方法接受一個函數,這個函數有兩個參數,第一個參數是上次函數執行的返回值(也稱為中間結果),第二個參數是stream中的元素,這個函數把這兩個值相加,得到的和會被賦值給下次執行這個函數的第一個參數。要注意的是:**第一次執行的時候第一個參數的值是Stream的第一個元素,第二個參數是Stream的第二個元素**。這個(ge)方(fang)法返回(hui)值類型(xing)是Optional,這是Java8防止出現(xian)NPE的(de)一(yi)種(zhong)可行(xing)方(fang)法,后面的(de)文(wen)章會詳細介紹,這里就簡單的(de)認為(wei)是一(yi)個(ge)容(rong)器,其中可能會包(bao)含0個(ge)或者1個(ge)對象。
這個過程可視(shi)化的結果如圖(tu):

reduce方(fang)法還有一個(ge)很(hen)常用的變種(zhong):
T reduce(T identity, BinaryOperator<T> accumulator);
這個定義上上面已經介紹過的基本一致,不同的是:它允許用戶提供一個循環計算的初始值,如果Stream為空,就直接返回該值。而且這個方法不(bu)會(hui)返回Optional,因(yin)為其不(bu)會(hui)出(chu)現null值。下面(mian)直接給出(chu)例子,就(jiu)不(bu)再做說明(ming)了。
List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10);
System.out.println("ints sum is:" + ints.stream().reduce(0, (sum, item) -> sum + item));
– count方法:獲(huo)取(qu)Stream中元(yuan)素的個數。比較簡單,這里就直接給出例子,不做解釋了。
List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10);
System.out.println("ints sum is:" + ints.stream().count());
– 搜索相關
– allMatch:是不(bu)是Stream中的所有元(yuan)素都滿(man)足給(gei)定(ding)的匹(pi)配條件
– anyMatch:Stream中是否存在任何一個元素(su)滿足(zu)匹配條件(jian)
– findFirst: 返回(hui)Stream中的第一(yi)個元素,如(ru)果Stream為空,返回(hui)空Optional
– noneMatch:是不是Stream中的(de)所有(you)元(yuan)素(su)都不滿足給定的(de)匹配條件
– max和min:使(shi)用(yong)給定的(de)比較(jiao)器(Operator),返(fan)回(hui)Stream中的(de)最大|最小值
下面(mian)給(gei)出allMatch和max的例子,剩下的方(fang)法讀者當成(cheng)練習。
查看源代碼打印幫助
List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10);
System.out.println(ints.stream().allMatch(item -> item < 100));
ints.stream().max((o1, o2) -> o1.compareTo(o2)).ifPresent(System.out::println);