Java基礎(chǔ)之Lambda表達(dá)式

# 函數(shù)式編程思想概述


在數(shù)學(xué)中,**函數(shù)**就是有輸入量、輸出量的一套計(jì)算方案瘪校,也就是“拿什么東西做什么事情”。相對(duì)而言月帝,面向?qū)ο筮^(guò)分強(qiáng)調(diào)“必須通過(guò)對(duì)象的形式來(lái)做事情”,而函數(shù)式思想則盡量忽略面向?qū)ο蟮膹?fù)雜語(yǔ)法——**強(qiáng)調(diào)做什么幽污,而不是以什么形式做**嚷辅。

面向?qū)ο蟮乃枷?

????做一件事情,找一個(gè)能解決這個(gè)事情的對(duì)象,調(diào)用對(duì)象的方法,完成事情.

函數(shù)式編程思想:

????只要能獲取到結(jié)果,誰(shuí)去做的,怎么做的都不重要,重視的是結(jié)果,不重視過(guò)程

# 冗余的Runnable代碼

## 傳統(tǒng)寫法

當(dāng)需要啟動(dòng)一個(gè)線程去完成任務(wù)時(shí),通常會(huì)通過(guò)`java.lang.Runnable`接口來(lái)定義任務(wù)內(nèi)容距误,并使用`java.lang.Thread`類來(lái)啟動(dòng)該線程簸搞。代碼如下:

```java

public?class?Demo01Runnable?{

????public?static?void?main(String[]?args)?{

????????//?匿名內(nèi)部類

????????Runnable?task?=?new?Runnable()?{

????????????@Override

????????????public?void?run()?{?//?覆蓋重寫抽象方法

????????????????System.out.println("多線程任務(wù)執(zhí)行!");

????????????}

????????};

????????new?Thread(task).start();?//?啟動(dòng)線程

????}

}

```

本著“一切皆對(duì)象”的思想准潭,這種做法是無(wú)可厚非的:首先創(chuàng)建一個(gè)`Runnable`接口的匿名內(nèi)部類對(duì)象來(lái)指定任務(wù)內(nèi)容趁俊,再將其交給一個(gè)線程來(lái)啟動(dòng)。

## 代碼分析

對(duì)于`Runnable`的匿名內(nèi)部類用法惋鹅,可以分析出幾點(diǎn)內(nèi)容:

-?`Thread`類需要`Runnable`接口作為參數(shù)则酝,其中的抽象`run`方法是用來(lái)指定線程任務(wù)內(nèi)容的核心;

-?為了指定`run`的方法體闰集,**不得不**需要`Runnable`接口的實(shí)現(xiàn)類;

-?為了省去定義一個(gè)`RunnableImpl`實(shí)現(xiàn)類的麻煩般卑,**不得不**使用匿名內(nèi)部類武鲁;

-?必須覆蓋重寫抽象`run`方法,所以方法名稱蝠检、方法參數(shù)沐鼠、方法返回值**不得不**再寫一遍,且不能寫錯(cuò)叹谁;

-?而實(shí)際上饲梭,**似乎只有方法體才是關(guān)鍵所在**。

# 編程思想轉(zhuǎn)換

## 做什么焰檩,而不是怎么做

我們真的希望創(chuàng)建一個(gè)匿名內(nèi)部類對(duì)象嗎憔涉?不。我們只是為了做某件事情而**不得不**創(chuàng)建一個(gè)對(duì)象析苫。我們真正希望做的事情是:將`run`方法體內(nèi)的代碼傳遞給`Thread`類知曉兜叨。

**傳遞一段代碼**——這才是我們真正的目的穿扳。而創(chuàng)建對(duì)象只是受限于面向?qū)ο笳Z(yǔ)法而不得不采取的一種手段方式。那国旷,有沒有更加簡(jiǎn)單的辦法矛物?如果我們將關(guān)注點(diǎn)從“怎么做”回歸到“做什么”的本質(zhì)上,就會(huì)發(fā)現(xiàn)只要能夠更好地達(dá)到目的跪但,過(guò)程與形式其實(shí)并不重要履羞。

# 體驗(yàn)Lambda的更優(yōu)寫法

2014年3月Oracle所發(fā)布的Java?8(JDK?1.8)中,加入了**Lambda表達(dá)式**的重量級(jí)新特性屡久。借助Java?8的全新語(yǔ)法吧雹,上述`Runnable`接口的匿名內(nèi)部類寫法可以通過(guò)更簡(jiǎn)單的Lambda表達(dá)式達(dá)到等效:

```java

public?class?Demo02LambdaRunnable?{

????public?static?void?main(String[]?args)?{

????????new?Thread(()?->?System.out.println("多線程任務(wù)執(zhí)行!")).start();?//?啟動(dòng)線程

????}

}

```

這段代碼和剛才的執(zhí)行效果是完全一樣的涂身,可以在1.8或更高的編譯級(jí)別下通過(guò)雄卷。從代碼的語(yǔ)義中可以看出:我們啟動(dòng)了一個(gè)線程,而線程任務(wù)的內(nèi)容以一種更加簡(jiǎn)潔的形式被指定蛤售。

不再有“不得不創(chuàng)建接口對(duì)象”的束縛丁鹉,不再有“抽象方法覆蓋重寫”的負(fù)擔(dān)。

# Lambda標(biāo)準(zhǔn)格式

Lambda省去面向?qū)ο蟮臈l條框框悴能,格式由**3個(gè)部分**組成:

*?一些參數(shù)

*?一個(gè)箭頭

*?一段代碼

Lambda表達(dá)式的**標(biāo)準(zhǔn)格式**為:

```

(參數(shù)類型?參數(shù)名稱)?->?{?代碼語(yǔ)句?}

```

格式說(shuō)明:

*?小括號(hào)內(nèi)的語(yǔ)法與傳統(tǒng)方法參數(shù)列表一致:無(wú)參數(shù)則留空揣钦;多個(gè)參數(shù)則用逗號(hào)分隔。

*?`->`是新引入的語(yǔ)法格式漠酿,代表指向動(dòng)作冯凹。

*?大括號(hào)內(nèi)的語(yǔ)法與傳統(tǒng)方法體要求基本一致。

# 練習(xí):使用Lambda標(biāo)準(zhǔn)格式(無(wú)參無(wú)返回)

###?題目

給定一個(gè)廚子`Cook`接口炒嘲,內(nèi)含唯一的抽象方法`makeFood`宇姚,且無(wú)參數(shù)、無(wú)返回值夫凸。如下:

```java

public?interface?Cook?{

????void?makeFood();

}

```

在下面的代碼中浑劳,請(qǐng)使用Lambda的**標(biāo)準(zhǔn)格式**調(diào)用`invokeCook`方法,打印輸出“吃飯啦夭拌!”字樣:

```java

public?class?Demo05InvokeCook?{

????public?static?void?main(String[]?args)?{

????????//?TODO?請(qǐng)?jiān)诖耸褂肔ambda【標(biāo)準(zhǔn)格式】調(diào)用invokeCook方法

????}

????private?static?void?invokeCook(Cook?cook)?{

????????cook.makeFood();

????}

}

```

###?解答

```java

public?static?void?main(String[]?args)?{

????invokeCook(()?->?{

????????System.out.println("吃飯啦魔熏!");

????});

}

```

>?備注:小括號(hào)代表`Cook`接口`makeFood`抽象方法的參數(shù)為空,大括號(hào)代表`makeFood`的方法體鸽扁。

# Lambda的參數(shù)和返回值

```

需求:

????使用數(shù)組存儲(chǔ)多個(gè)Person對(duì)象

????對(duì)數(shù)組中的Person對(duì)象使用Arrays的sort方法通過(guò)年齡進(jìn)行升序排序

```

下面舉例演示`java.util.Comparator<T>`接口的使用場(chǎng)景代碼蒜绽,其中的抽象方法定義為:

*?`public?abstract?int?compare(T?o1,?T?o2);`

當(dāng)需要對(duì)一個(gè)對(duì)象數(shù)組進(jìn)行排序時(shí),`Arrays.sort`方法需要一個(gè)`Comparator`接口實(shí)例來(lái)指定排序的規(guī)則桶现。假設(shè)有一個(gè)`Person`類躲雅,含有`String?name`和`int?age`兩個(gè)成員變量:

```java

public?class?Person?{?

????private?String?name;

????private?int?age;


????//?省略構(gòu)造器、toString方法與Getter?Setter?

}

```

###?傳統(tǒng)寫法

如果使用傳統(tǒng)的代碼對(duì)`Person[]`數(shù)組進(jìn)行排序巩那,寫法如下:

```java

import?java.util.Arrays;

import?java.util.Comparator;

public?class?Demo06Comparator?{

????public?static?void?main(String[]?args)?{

????????//?本來(lái)年齡亂序的對(duì)象數(shù)組

????????Person[]?array?=?{

????????????new?Person("古力娜扎",?19),

????????????new?Person("迪麗熱巴",?18),

????????????new?Person("馬爾扎哈",?20)?};

????????//?匿名內(nèi)部類

????????Comparator<Person>?comp?=?new?Comparator<Person>()?{

????????????@Override

????????????public?int?compare(Person?o1,?Person?o2)?{

????????????????return?o1.getAge()?-?o2.getAge();

????????????}

????????};

????????Arrays.sort(array,?comp);?//?第二個(gè)參數(shù)為排序規(guī)則吏夯,即Comparator接口實(shí)例

????????for?(Person?person?:?array)?{

????????????System.out.println(person);

????????}

????}

}

```

這種做法在面向?qū)ο蟮乃枷胫写蓑冢坪跻彩恰袄硭?dāng)然”的。其中`Comparator`接口的實(shí)例(使用了匿名內(nèi)部類)代表了“按照年齡從小到大”的排序規(guī)則噪生。

## 代碼分析

下面我們來(lái)搞清楚上述代碼真正要做什么事情裆赵。

-?為了排序,`Arrays.sort`方法需要排序規(guī)則跺嗽,即`Comparator`接口的實(shí)例战授,抽象方法`compare`是關(guān)鍵;

-?為了指定`compare`的方法體桨嫁,**不得不**需要`Comparator`接口的實(shí)現(xiàn)類植兰;

-?為了省去定義一個(gè)`ComparatorImpl`實(shí)現(xiàn)類的麻煩,**不得不**使用匿名內(nèi)部類璃吧;

-?必須覆蓋重寫抽象`compare`方法楣导,所以方法名稱、方法參數(shù)畜挨、方法返回值**不得不**再寫一遍筒繁,且不能寫錯(cuò);

-?實(shí)際上巴元,**只有參數(shù)和方法體才是關(guān)鍵**毡咏。

###?Lambda寫法

```java

import?java.util.Arrays;

public?class?Demo07ComparatorLambda?{

????public?static?void?main(String[]?args)?{

????????Person[]?array?=?{

????????????new?Person("古力娜扎",?19),

????????????new?Person("迪麗熱巴",?18),

????????????new?Person("馬爾扎哈",?20)?};

????????Arrays.sort(array,?(Person?a,?Person?b)?->?{

????????????return?a.getAge()?-?b.getAge();

????????});

????????for?(Person?person?:?array)?{

????????????System.out.println(person);

????????}

????}

}

```

# 練習(xí):使用Lambda標(biāo)準(zhǔn)格式(有參有返回)

###?題目

給定一個(gè)計(jì)算器`Calculator`接口,內(nèi)含抽象方法`calc`可以將兩個(gè)int數(shù)字相加得到和值:

```java

public?interface?Calculator?{

????int?calc(int?a,?int?b);

}

```

在下面的代碼中逮刨,請(qǐng)使用Lambda的**標(biāo)準(zhǔn)格式**調(diào)用`invokeCalc`方法呕缭,完成120和130的相加計(jì)算:

```java

public?class?Demo08InvokeCalc?{

????public?static?void?main(String[]?args)?{

????????//?TODO?請(qǐng)?jiān)诖耸褂肔ambda【標(biāo)準(zhǔn)格式】調(diào)用invokeCalc方法來(lái)計(jì)算120+130的結(jié)果?

????}

????private?static?void?invokeCalc(int?a,?int?b,?Calculator?calculator)?{

????????int?result?=?calculator.calc(a,?b);

????????System.out.println("結(jié)果是:"?+?result);

????}

}

```

## 解答

```java

public?static?void?main(String[]?args)?{

????invokeCalc(120,?130,?(int?a,?int?b)?->?{

????????return?a?+?b;

????});

}

```

>?備注:小括號(hào)代表`Calculator`接口`calc`抽象方法的參數(shù),大括號(hào)代表`calc`的方法體修己。

# Lambda省略格式

###?可推導(dǎo)即可省略

Lambda強(qiáng)調(diào)的是“做什么”而不是“怎么做”恢总,所以凡是可以根據(jù)上下文推導(dǎo)得知的信息,都可以省略箩退。例如上例還可以使用Lambda的省略寫法:

```java

public?static?void?main(String[]?args)?{

????invokeCalc(120,?130,?(a,?b)?->?a?+?b);

}

```

## 省略規(guī)則

在Lambda標(biāo)準(zhǔn)格式的基礎(chǔ)上离熏,使用省略寫法的規(guī)則為:

1.?小括號(hào)內(nèi)參數(shù)的類型可以省略;

2.?如果小括號(hào)內(nèi)**有且僅有一個(gè)參**戴涝,則小括號(hào)可以省略;

3.?如果大括號(hào)內(nèi)**有且僅有一個(gè)語(yǔ)句**钻蔑,則無(wú)論是否有返回值啥刻,都可以省略大括號(hào)、return關(guān)鍵字及語(yǔ)句分號(hào)咪笑。

>?備注:掌握這些省略規(guī)則后可帽,請(qǐng)對(duì)應(yīng)地回顧本章開頭的多線程案例。

# 練習(xí):使用Lambda省略格式

## 題目

仍然使用前文含有唯一`makeFood`抽象方法的廚子`Cook`接口窗怒,在下面的代碼中映跟,請(qǐng)使用Lambda的**省略格式**調(diào)用`invokeCook`方法蓄拣,打印輸出“吃飯啦!”字樣:

```java

public?class?Demo09InvokeCook?{

????public?static?void?main(String[]?args)?{

????????//?TODO?請(qǐng)?jiān)诖耸褂肔ambda【省略格式】調(diào)用invokeCook方法

????}

????private?static?void?invokeCook(Cook?cook)?{

????????cook.makeFood();

????}

}

```

## 解答

```java

public?static?void?main(String[]?args)?{

????invokeCook(()?->?System.out.println("吃飯啦努隙!"));

}

```

# Lambda的使用前提

Lambda的語(yǔ)法非常簡(jiǎn)潔球恤,完全沒有面向?qū)ο髲?fù)雜的束縛。但是使用時(shí)有幾個(gè)問(wèn)題需要特別注意:

1.?使用Lambda必須具有接口荸镊,且要求**接口中有且僅有一個(gè)抽象方法**咽斧。

???無(wú)論是JDK內(nèi)置的`Runnable`、`Comparator`接口還是自定義的接口躬存,只有當(dāng)接口中的抽象方法存在且唯一時(shí)张惹,才可以使用Lambda。

2.?使用Lambda必須具有**上下文推斷**岭洲。

???也就是方法的參數(shù)或局部變量類型必須為L(zhǎng)ambda對(duì)應(yīng)的接口類型宛逗,才能使用Lambda作為該接口的實(shí)例。

>?備注:有且僅有一個(gè)抽象方法的接口盾剩,稱為“**函數(shù)式接口**”雷激。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市彪腔,隨后出現(xiàn)的幾起案子侥锦,更是在濱河造成了極大的恐慌,老刑警劉巖德挣,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件恭垦,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡格嗅,警方通過(guò)查閱死者的電腦和手機(jī)番挺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)屯掖,“玉大人玄柏,你說(shuō)我怎么就攤上這事√” “怎么了粪摘?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)绍坝。 經(jīng)常有香客問(wèn)我徘意,道長(zhǎng),這世上最難降的妖魔是什么轩褐? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任椎咧,我火速辦了婚禮,結(jié)果婚禮上把介,老公的妹妹穿的比我還像新娘勤讽。我一直安慰自己蟋座,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布脚牍。 她就那樣靜靜地躺著向臀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪莫矗。 梳的紋絲不亂的頭發(fā)上飒硅,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音作谚,去河邊找鬼三娩。 笑死,一個(gè)胖子當(dāng)著我的面吹牛妹懒,可吹牛的內(nèi)容都是我干的雀监。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼眨唬,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼会前!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起匾竿,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤瓦宜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后岭妖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體临庇,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年昵慌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了假夺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡斋攀,死狀恐怖已卷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情淳蔼,我是刑警寧澤侧蘸,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站鹉梨,受9級(jí)特大地震影響闺魏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜俯画,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望司草。 院中可真熱鬧艰垂,春花似錦泡仗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至胰柑,卻和暖如春截亦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背柬讨。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工崩瓤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人踩官。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓却桶,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親蔗牡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子颖系,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容