# 函數(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ù)式接口**”雷激。