Java8特性詳解 lambda表達式(一):使用篇

在 Java 8之前,一個實現(xiàn)了只有一個抽象方法的接口的匿名類看起來更像Lambda 表達式泞当。下面的代碼中洪鸭,anonymousClass方法調用waitFor方法,參數(shù)是一個實現(xiàn)接口的Condition類蒜撮,實現(xiàn)的功能為醋拧,當滿足某些條件,Server 就會關閉淀弹。 下面的代碼是典型的匿名類的使用丹壕。

void anonymousClass() {
    final Server server = new HttpServer();
    waitFor(new Condition() {
        @Override
        public Boolean isSatisfied() {
            return !server.isRunning();
        }
    }
復制代碼

![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "點擊并拖拽以移動")

下面的代碼用 Lambda 表達式實現(xiàn)相同的功能:

void closure() { 
     Server server = new HttpServer();
     waitFor(() -> !server.isRunning()); 
 }
復制代碼

![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "點擊并拖拽以移動")

其實,上面的waitFor方法薇溃,更接近于下面的代碼的描述:

class WaitFor {
    static void waitFor(Condition condition) throws   
    InterruptedException {
        while (!condition.isSatisfied())
            Thread.sleep(250);
    }
}
復制代碼

![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "點擊并拖拽以移動")

一些理論上的區(qū)別 實際上菌赖,上面的兩種方法的實現(xiàn)都是閉包,后者的實現(xiàn)就是Lambda 表示式沐序。這就意味著兩者都需要持有運行時的環(huán)境琉用。在 Java 8 之前,這就需要把匿名類所需要的一切復制給它策幼。在上面的例子中邑时,就需要把 server 屬性復制給匿名類。

因為是復制特姐,變量必須聲明為 final 類型晶丘,以保證在獲取和使用時不會被改變。Java 使用了優(yōu)雅的方式保證了變量不會被更新,所以我們不用顯式地把變量加上 final 修飾浅浮。

Lambda 表達式則不需要拷貝變量到它的運行環(huán)境中纱控,從而 Lambda 表達式被當做是一個真正的方法來對待饺鹃,而不是一個類的實例。

Lambda 表達式不需要每次都要被實例化,對于 Java 來說鲫售,帶來巨大的好處验懊。不像實例化匿名類缩膝,對內存的影響可以降到最小惊来。

總體來說,匿名方法和匿名類存在以下區(qū)別:

類必須實例化桐腌,而方法不必痊末; 當一個類被新建時,需要給對象分配內存哩掺; 方法只需要分配一次內存凿叠,它被存儲在堆的永久區(qū)內; 對象作用于它自己的數(shù)據(jù)嚼吞,而方法不會盒件; 靜態(tài)類里的方法類似于匿名方法的功能。

一些具體的區(qū)別 匿名方法和匿名類有一些具體的區(qū)別舱禽,主要包括獲取語義和覆蓋變量炒刁。

獲取語義 this 關鍵字是其中的一個語義上的區(qū)別。在匿名類中誊稚,this 指的是匿名類的實例翔始,例如有了內部類為 Foo$InnerClass,當你引用內部類閉包的作用域時里伯,像Foo.this.x的代碼看起來就有些奇怪城瞎。 在 Lambda 表達式中,this 指的就是閉包作用域疾瓮,事實上脖镀,Lambda 表達式就是一個作用域,這就意味著你不需要從超類那里繼承任何名字狼电,或是引入作用域的層級蜒灰。你可以在作用域里直接訪問屬性,方法和局部變量肩碟。 例如强窖,下面的代碼中,Lambda 表達式可以直接訪問firstName變量削祈。

public class Example {
    private String firstName = "Tom";

    public void example() {
        Function<String, String> addSurname = surname -> {
            // equivalent to this.firstName
            return firstName + " " + surname;  // or even,   
        };
    }
}
復制代碼

![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "點擊并拖拽以移動")

這里的firstName就是this.firstName的簡寫翅溺。 但是在匿名類中,你必須顯式地調用firstName,

public class Example {
    private String firstName = "Jerry";

    public void anotherExample() {
        Function<String, String> addSurname = new Function<String,  
        String>() {
            @Override
            public String apply(String surname) {
                return Example.this.firstName + " " + surname;   
            }
        };
    }
}

![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "點擊并拖拽以移動")

1.lambda表達式

Java8最值得學習的特性就是Lambda表達式和Stream API未巫,如果有python或者javascript的語言基礎窿撬,對理解Lambda表達式有很大幫助启昧,因為Java正在將自己變的更高(Sha)級(Gua)叙凡,更人性化。--------可以這么說lambda表達式其實就是實現(xiàn)SAM接口的語法糖密末。

lambda寫的好可以極大的減少代碼冗余握爷,同時可讀性也好過冗長的內部類,匿名類严里。

先列舉兩個常見的簡化(簡單的代碼同樣好理解)

  • 創(chuàng)建線程

![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "點擊并拖拽以移動")

  • 排序

![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "點擊并拖拽以移動")

lambda表達式配合Java8新特性Stream API可以將業(yè)務功能通過函數(shù)式編程簡潔的實現(xiàn)新啼。(為后面的例子做鋪墊)

例如:

![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "點擊并拖拽以移動")

這段代碼就是對一個字符串的列表,把其中包含的每個字符串都轉換成全小寫的字符串刹碾。注意代碼第四行的map方法調用燥撞,這里map方法就是接受了一個lambda表達式。

1.1lambda表達式語法

1.1.1lambda表達式的一般語法

<pre>(Type1 param1, Type2 param2, ..., TypeN paramN) -> {
statment1;
statment2;
//.............
return statmentM;
}</pre>

這是lambda表達式的完全式語法迷帜,后面幾種語法是對它的簡化物舒。

1.1.2單參數(shù)語法

<pre>param1 -> {
statment1;
statment2;
//.............
return statmentM;
}</pre>

當lambda表達式的參數(shù)個數(shù)只有一個,可以省略小括號

例如:將列表中的字符串轉換為全小寫

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和語句結尾的分號

例如:將列表中的字符串轉換為全小寫

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一樣是Java8新語言特性,后面會講到)

Class or instance :: method

例如:將列表中的字符串轉換為全小寫

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

變量waibu :外部變量

變量chuandi :傳遞變量

變量zidingyi :內部自定義變量

lambda表達式可以訪問給它傳遞的變量锦针,訪問自己內部定義的變量荠察,同時也能訪問它外部的變量。

不過lambda表達式訪問外部變量有一個非常重要的限制:變量不可變(只是引用不可變奈搜,而不是真正的不可變)悉盆。

當在表達式內部修改waibu = waibu + " ";時,IDE就會提示你:

Local variable waibu defined in an enclosing scope must be final or effectively final

編譯時會報錯馋吗。因為變量waibu被lambda表達式引用舀瓢,所以編譯器會隱式的把其當成final來處理。

以前Java的匿名內部類在訪問外部變量的時候耗美,外部變量必須用final修飾【┧瑁現(xiàn)在java8對這個限制做了優(yōu)化,可以不用顯示使用final修飾商架,但是編譯器隱式當成final來處理堰怨。

1.3lambda表達式中的this概念

在lambda中,this不是指向lambda表達式產生的那個SAM對象蛇摸,而是聲明它的外部對象备图。

例如:

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.方法引用和構造器引用

本人認為是進一步簡化lambda表達式的聲明的一種語法糖。

前面的例子中已有使用到: execStrs.forEach(System.out::println);

2.1方法引用

objectName::instanceMethod

ClassName::staticMethod

ClassName::instanceMethod

前兩種方式類似,等同于把lambda表達式的參數(shù)直接當成instanceMethod|staticMethod的參數(shù)來調用揽涮。比如System.out::println等同于x->System.out.println(x)抠藕;Math::max等同于(x, y)->Math.max(x,y)。

最后一種方式蒋困,等同于把lambda表達式的第一個參數(shù)當成instanceMethod的目標對象,其他剩余參數(shù)當成該方法的參數(shù)零院。比如String::toLowerCase等同于x->x.toLowerCase()村刨。

可以這么理解,前兩種是將傳入對象當參數(shù)執(zhí)行方法嵌牺,后一種是調用傳入對象的方法。

2.2構造器引用

構造器引用語法如下:ClassName::new募疮,把lambda表達式的參數(shù)當成ClassName構造器的參數(shù) 枯饿。例如BigDecimal::new等同于x->new BigDecimal(x)。

3.Stream語法

兩句話理解Stream:

1.Stream是元素的集合搔扁,這點讓Stream看起來用些類似Iterator;
2.可以支持順序和并行的對原Stream進行匯聚的操作蟋字;

大家可以把Stream當成一個裝飾后的Iterator稿蹲。原始版本的Iterator,用戶只能逐個遍歷元素并對其執(zhí)行某些操作鹊奖;包裝后的Stream苛聘,用戶只要給出需要對其包含的元素執(zhí)行什么操作,比如“過濾掉長度大于10的字符串”忠聚、“獲取每個字符串的首字母”等设哗,具體這些操作如何應用到每個元素上,就給Stream就好了两蟀!原先是人告訴計算機一步一步怎么做网梢,現(xiàn)在是告訴計算機做什么,計算機自己決定怎么做赂毯。當然這個“怎么做”還是比較弱的战虏。

例子:

//Lists是Guava中的一個工具類
List<Integer> nums = Lists.newArrayList(1,null,3,4,null,6);
nums.stream().filter(num -> num != null).count();

上面這段代碼是獲取一個List中拣宰,元素不為null的個數(shù)。這段代碼雖然很簡短烦感,但是卻是一個很好的入門級別的例子來體現(xiàn)如何使用Stream巡社,正所謂“麻雀雖小五臟俱全”。我們現(xiàn)在開始深入解刨這個例子手趣,完成以后你可能可以基本掌握Stream的用法晌该!

![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "點擊并拖拽以移動")

圖片就是對于Stream例子的一個解析,可以很清楚的看見:原本一條語句被三種顏色的框分割成了三個部分回懦。紅色框中的語句是一個Stream的生命開始的地方气笙,負責創(chuàng)建一個Stream實例次企;綠色框中的語句是賦予Stream靈魂的地方怯晕,把一個Stream轉換成另外一個Stream,紅框的語句生成的是一個包含所有nums變量的Stream缸棵,進過綠框的filter方法以后吧凉,重新生成了一個過濾掉原nums列表所有null以后的Stream阀捅;藍色框中的語句是豐收的地方饲鄙,把Stream的里面包含的內容按照某種算法來匯聚成一個值,例子中是獲取Stream中包含的元素個數(shù)伪朽。如果這樣解析以后朴肺,還不理解戈稿,那就只能動用“核武器”–圖形化器瘪,一圖抵千言援所!

![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "點擊并拖拽以移動")

使用Stream的基本步驟:

1.創(chuàng)建Stream住拭;
2.轉換Stream滔岳,每次轉換原有Stream對象不改變,返回一個新的Stream對象(可以有多次轉換)刘离;
3.對Stream進行聚合(Reduce)操作硫惕,獲取想要的結果恼除;

3.1怎么得到Stream

最常用的創(chuàng)建Stream有兩種途徑:

1.通過Stream接口的靜態(tài)工廠方法(注意:Java8里接口可以帶靜態(tài)方法)豁辉;
2.通過Collection接口的默認方法(默認方法:Default method秋忙,也是Java8中的一個新特性灰追,就是接口中的一個帶有實現(xiàn)的方法)–stream()弹澎,把一個Collection對象轉換成Stream

3.1.1 使用Stream靜態(tài)方法來創(chuàng)建Stream

1. of方法:有兩個overload方法,一個接受變長參數(shù)佩迟,一個接口單一值

Stream<Integer> integerStream = Stream.of(1, 2, 3, 5);
Stream<String> stringStream = Stream.of("taobao");

2. generator方法:生成一個無限長度的Stream,其元素的生成是通過給定的Supplier(這個接口可以看成一個對象的工廠灸姊,每次調用返回一個給定類型的對象)

Stream.generate(new Supplier<Double>() {
@Override
public Double get() {
return Math.random();
}
});

Stream.generate(() -> Math.random());
Stream.generate(Math::random);
三條語句的作用都是一樣的,只是使用了lambda表達式和方法引用的語法來簡化代碼父晶。每條語句其實都是生成一個無限長度的Stream甲喝,其中值是隨機的俺猿。這個無限長度Stream是懶加載,一般這種無限長度的Stream都會配合Stream的limit()方法來用凯肋。

3. iterate方法:也是生成無限長度的Stream侮东,和generator不同的是,其元素的生成是重復對給定的種子值(seed)調用用戶指定函數(shù)來生成的铁蹈。其中包含的元素可以認為是:seed容诬,f(seed),f(f(seed))無限循環(huán)

Stream.iterate(1, item -> item + 1).limit(10).forEach(System.out::println);
這段代碼就是先獲取一個無限長度的正整數(shù)集合的Stream览徒,然后取出前10個打印习蓬。千萬記住使用limit方法躲叼,不然會無限打印下去押赊。

3.1.2通過Collection子類獲取Stream

Collection接口有一個stream方法流礁,所以其所有子類都都可以獲取對應的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中包含的元素進行去重操作(去重邏輯依賴元素的equals方法)萝快,新生成的Stream中沒有重復的元素;

![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "點擊并拖拽以移動")

2. filter: 對于Stream中包含的元素使用給定的過濾函數(shù)進行過濾操作奄容,新生成的Stream只包含符合條件的元素;

![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "點擊并拖拽以移動")

3. map: 對于Stream中包含的元素使用給定的轉換函數(shù)進行轉換操作叁怪,新生成的Stream只包含轉換生成的元素涣觉。這個方法有三個對于原始類型的變種方法官册,分別是:mapToInt膝宁,mapToLong和mapToDouble员淫。這三個方法也比較好理解击敌,比如mapToInt就是把原始Stream轉換成一個新的Stream圣蝎,這個新生成的Stream中的元素都是int類型。之所以會有這樣三個變種方法鞍陨,可以免除自動裝箱/拆箱的額外消耗;

![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "點擊并拖拽以移動")

4. flatMap:和map類似寿烟,不同的是其每個元素轉換得到的是Stream對象辛燥,會把子Stream中的元素壓縮到父集合中筛武;

![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "點擊并拖拽以移動")

flatMap給一段代碼理解:

<pre>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());</pre>

flatMap 把 input Stream 中的層級結構扁平化,就是將最底層元素抽出來放到一起挎塌,最終 output 的新 Stream 里面已經沒有 List 了徘六,都是直接的數(shù)字。

5. peek: 生成一個包含原Stream的所有元素的新Stream榴都,同時會提供一個消費函數(shù)(Consumer實例)待锈,新Stream每個元素被消費的時候都會執(zhí)行給定的消費函數(shù);

![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "點擊并拖拽以移動")

6. limit: 對一個Stream進行截斷操作和屎,獲取其前N個元素,如果原Stream中包含的元素個數(shù)小于N线罕,那就獲取其所有的元素;

![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "點擊并拖拽以移動")

7. skip: 返回一個丟棄原Stream的前N個元素后剩下元素組成的新Stream,如果原Stream中包含的元素個數(shù)小于N,那么返回空Stream蛮浑;

![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "點擊并拖拽以移動")

整體調用例子:

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());

這段代碼演示了上面介紹的所有轉換方法(除了flatMap),簡單解釋一下這段代碼的含義:給定一個Integer類型的List徐伐,獲取其對應的Stream對象性穿,然后進行過濾掉null呆万,再去重,再每個元素乘以2,再每個元素被消費的時候打印自身,在跳過前兩個元素,最后去前四個元素進行加和運算(解釋一大堆驻襟,很像廢話,因為基本看了方法名就知道要做什么了栋艳。這個就是聲明式編程的一大好處!)遍略。大家可以參考上面對于每個方法的解釋,看看最終的輸出是什么盹愚。

可能會有這樣的疑問:在對于一個Stream進行多次轉換操作礁鲁,每次都對Stream的每個元素進行轉換,而且是執(zhí)行多次,這樣時間復雜度就是一個for循環(huán)里把所有操作都做掉的N(轉換的次數(shù))倍啊淌山。其實不是這樣的移稳,轉換操作都是lazy的体斩,多個轉換操作只會在匯聚操作(見下節(jié))的時候融合起來,一次循環(huán)完成泣懊。我們可以這樣簡單的理解,Stream里有個操作函數(shù)的集合惠奸,每次轉換操作就是把轉換函數(shù)放入這個集合中梅誓,在匯聚操作的時候循環(huán)Stream對應的集合,然后對每個元素執(zhí)行所有的函數(shù)佛南。

3.3匯聚(Reduce)Stream

匯聚操作(也稱為折疊)接受一個元素序列為輸入梗掰,反復使用某個合并操作,把序列中的元素合并成一個匯總的結果嗅回。比如查找一個數(shù)字列表的總和或者最大值及穗,或者把這些數(shù)字累積成一個List對象。Stream接口有一些通用的匯聚操作绵载,比如reduce()和collect()埂陆;也有一些特定用途的匯聚操作苛白,比如sum(),max()和count()。注意:sum方法不是所有的Stream對象都有的焚虱,只有IntStream购裙、LongStream和DoubleStream是實例才有。

下面會分兩部分來介紹匯聚操作:

可變匯聚:把輸入的元素們累積到一個可變的容器中鹃栽,比如Collection或者StringBuilder躏率;
其他匯聚:除去可變匯聚剩下的,一般都不是通過反復修改某個可變對象民鼓,而是通過把前一次的匯聚結果當成下一次的入?yún)⑥敝ィ磸腿绱恕1热鐁educe丰嘉,count夯到,allMatch;

3.3.1可變匯聚

可變匯聚對應的只有一個方法:collect供嚎,正如其名字顯示的黄娘,它可以把Stream中的要有元素收集到一個結果容器中(比如Collection)。先看一下最通用的collect方法的定義(還有其他override方法):

<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
先來看看這三個參數(shù)的含義:Supplier supplier是一個工廠函數(shù)克滴,用來生成一個新的容器逼争;BiConsumer accumulator也是一個函數(shù),用來把Stream中的元素添加到結果容器中劝赔;BiConsumer combiner還是一個函數(shù)誓焦,用來把中間狀態(tài)的多個結果容器合并成為一個(并發(fā)的時候會用到)∽琶保看暈了杂伟?來段代碼!

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));
上面這段代碼就是對一個元素是Integer類型的List仍翰,先過濾掉全部的null赫粥,然后把剩下的元素收集到一個新的List中。進一步看一下collect方法的三個參數(shù)予借,都是lambda形式的函數(shù)越平。

第一個函數(shù)生成一個新的ArrayList實例;
第二個函數(shù)接受兩個參數(shù)灵迫,第一個是前面生成的ArrayList對象秦叛,二個是stream中包含的元素,函數(shù)體就是把stream中的元素加入ArrayList對象中瀑粥。第二個函數(shù)被反復調用直到原stream的元素被消費完畢挣跋;
第三個函數(shù)也是接受兩個參數(shù),這兩個都是ArrayList類型的狞换,函數(shù)體就是把第二個ArrayList全部加入到第一個中避咆;
但是上面的collect方法調用也有點太復雜了舟肉,沒關系!我們來看一下collect方法另外一個override的版本牌借,其依賴[Collector](Collector (Java Platform SE 8 ))度气。

<R, A> R collect(Collector<? super T, A, R> collector);
這樣清爽多了!Java8還給我們提供了Collector的工具類–[Collectors](Collectors (Java Platform SE 8 ))膨报,其中已經定義了一些靜態(tài)工廠方法,比如:Collectors.toCollection()收集到Collection中, Collectors.toList()收集到List中和Collectors.toSet()收集到Set中适荣。這樣的靜態(tài)方法還有很多现柠,這里就不一一介紹了,大家可以直接去看JavaDoc弛矛。下面看看使用Collectors對于代碼的簡化:

List<Integer> numsWithoutNull = nums.stream().filter(num -> num != null).
collect(Collectors.toList());

3.3.2其他匯聚

– reduce方法:reduce方法非常的通用够吩,后面介紹的count,sum等都可以使用其實現(xiàn)丈氓。reduce方法有三個override的方法周循,本文介紹兩個最常用的。先來看reduce方法的第一種形式万俗,其方法定義如下:

Optional<T> reduce(BinaryOperator<T> accumulator);
接受一個BinaryOperator類型的參數(shù)湾笛,在使用的時候我們可以用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方法接受一個函數(shù)闰歪,這個函數(shù)有兩個參數(shù)嚎研,第一個參數(shù)是上次函數(shù)執(zhí)行的返回值(也稱為中間結果),第二個參數(shù)是stream中的元素库倘,這個函數(shù)把這兩個值相加临扮,得到的和會被賦值給下次執(zhí)行這個函數(shù)的第一個參數(shù)。要注意的是:第一次執(zhí)行的時候第一個參數(shù)的值是Stream的第一個元素教翩,第二個參數(shù)是Stream的第二個元素杆勇。這個方法返回值類型是Optional,這是Java8防止出現(xiàn)NPE的一種可行方法饱亿,后面的文章會詳細介紹蚜退,這里就簡單的認為是一個容器,其中可能會包含0個或者1個對象路捧。
這個過程可視化的結果如圖:

![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "點擊并拖拽以移動")

reduce方法還有一個很常用的變種:

T reduce(T identity, BinaryOperator<T> accumulator);
這個定義上上面已經介紹過的基本一致关霸,不同的是:它允許用戶提供一個循環(huán)計算的初始值,如果Stream為空杰扫,就直接返回該值队寇。而且這個方法不會返回Optional,因為其不會出現(xiàn)null值章姓。下面直接給出例子佳遣,就不再做說明了识埋。

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方法:獲取Stream中元素的個數(shù)。比較簡單零渐,這里就直接給出例子窒舟,不做解釋了。

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:是不是Stream中的所有元素都滿足給定的匹配條件
– anyMatch:Stream中是否存在任何一個元素滿足匹配條件
– findFirst: 返回Stream中的第一個元素诵盼,如果Stream為空惠豺,返回空Optional
– noneMatch:是不是Stream中的所有元素都不滿足給定的匹配條件
– max和min:使用給定的比較器(Operator),返回Stream中的最大|最小值
下面給出allMatch和max的例子风宁,剩下的方法讀者當成練習洁墙。

查看源代碼打印幫助
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);

參考文章

Java 中的 Lambda 表達式 - 掘金

Java8特性詳解 lambda表達式 Stream - aoeiuv - 博客園

微信公眾號【程序員黃小斜】作者是前螞蟻金服Java工程師,專注分享Java技術干貨和求職成長心得戒财,不限于BAT面試热监,算法、計算機基礎饮寞、數(shù)據(jù)庫孝扛、分布式、spring全家桶幽崩、微服務苦始、高并發(fā)、JVM歉铝、Docker容器盈简,ELK、大數(shù)據(jù)等太示。關注后回復【book】領取精選20本Java面試必備精品電子書柠贤。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市类缤,隨后出現(xiàn)的幾起案子臼勉,更是在濱河造成了極大的恐慌,老刑警劉巖餐弱,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宴霸,死亡現(xiàn)場離奇詭異,居然都是意外死亡膏蚓,警方通過查閱死者的電腦和手機瓢谢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來驮瞧,“玉大人氓扛,你說我怎么就攤上這事。” “怎么了采郎?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵千所,是天一觀的道長。 經常有香客問我蒜埋,道長淫痰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任整份,我火速辦了婚禮待错,結果婚禮上,老公的妹妹穿的比我還像新娘皂林。我一直安慰自己朗鸠,他們只是感情好,可當我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布础倍。 她就那樣靜靜地躺著,像睡著了一般胎挎。 火紅的嫁衣襯著肌膚如雪沟启。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天犹菇,我揣著相機與錄音德迹,去河邊找鬼。 笑死揭芍,一個胖子當著我的面吹牛胳搞,可吹牛的內容都是我干的。 我是一名探鬼主播称杨,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼肌毅,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了姑原?” 一聲冷哼從身側響起悬而,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎锭汛,沒想到半個月后笨奠,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡唤殴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年般婆,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片朵逝。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡蔚袍,死狀恐怖,靈堂內的尸體忽然破棺而出廉侧,到底是詐尸還是另有隱情页响,我是刑警寧澤篓足,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站闰蚕,受9級特大地震影響栈拖,放射性物質發(fā)生泄漏。R本人自食惡果不足惜没陡,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一涩哟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧盼玄,春花似錦贴彼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至童番,卻和暖如春精钮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背剃斧。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工轨香, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人幼东。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓臂容,卻偏偏與公主長得像,于是被迫代替她去往敵國和親根蟹。 傳聞我的和親對象是個殘疾皇子脓杉,可洞房花燭夜當晚...
    茶點故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內容