Java8 函數(shù)式編程詳解
Author:Dorae
Date:2017年11月1日23:03:26
轉(zhuǎn)載請(qǐng)注明出處
由于切換博客,2018/09/20及之前文章可能會(huì)存在圖片無(wú)法查看的問(wèn)題,請(qǐng)移步這里。
說(shuō)起Java8,可能很多人都已經(jīng)知道其最大的改進(jìn),就是引入了Lambda表達(dá)式與Stream,畢竟Java9都已近發(fā)布了,Java8發(fā)布了也已經(jīng)近三年。那么,今天我們就先來(lái)講一下Java8引入的Lambda表達(dá)式,以及由此引入的函數(shù)式編程,以及函數(shù)式接口。
什么是函數(shù)式編程
函數(shù)式編程并不是Java新提出的概念,其與指令編程相比,強(qiáng)調(diào)函數(shù)的計(jì)算比指令的計(jì)算更重要;與過(guò)程化編程相比,其中函數(shù)的計(jì)算可以隨時(shí)調(diào)用。
當(dāng)然,大家應(yīng)該都知道面向?qū)ο蟮奶匦裕ǔ橄蟆⒎庋b、繼承、多態(tài))。其實(shí)在Java8出現(xiàn)之前,我們關(guān)注的往往是某一類(lèi)對(duì)象應(yīng)該具有什么樣的屬性,當(dāng)然這也是面向?qū)ο蟮暮诵?-對(duì)數(shù)據(jù)進(jìn)行抽象。但是java8出現(xiàn)以后,這一點(diǎn)開(kāi)始出現(xiàn)變化,似乎在某種場(chǎng)景下,更加關(guān)注某一類(lèi)共有的行為(這似乎與之前的接口有些類(lèi)似),這也就是java8提出函數(shù)式編程的目的。如圖1-1所示,展示了面向?qū)ο缶幊痰矫嫦蛐袨榫幊痰淖兓?/p>
圖1-1
為什么需要Lambda表達(dá)式
首先,不得不提增加Lambda的目的,其實(shí)就是為了支持函數(shù)式編程,而為了支持Lambda表達(dá)式,才有了函數(shù)式接口。另外,為了在面對(duì)大型數(shù)據(jù)集合時(shí),為了能夠更加高效的開(kāi)發(fā),編寫(xiě)的代碼更加易于維護(hù),更加容易運(yùn)行在多核CPU上,java在語(yǔ)言層面增加了Lambda表達(dá)式。
第一個(gè)Lambda表達(dá)式
前邊廢話了這么多,其實(shí)Lambda就是Java新增的語(yǔ)法而已。當(dāng)然,Lambda(我們認(rèn)為這里包含了方法引用)確實(shí)能夠給我們的開(kāi)發(fā)帶來(lái)許多便利。
首先,在java8之前,如果需要建立一個(gè)線程,很大可能會(huì)寫(xiě)出下面的代碼:
new Thread(new Runnable()) {
@Override
public void run() {
System.out.println("Hello World!");
}
}).start();
但是Java8引入Lambda之后,也許這樣寫(xiě)會(huì)更好:
new Thread(
() -> System.out.println("Hello world!");
);
很明顯,Lambda可以幫助我們減少模板代碼的書(shū)寫(xiě),同時(shí)減少了要維護(hù)的匿名內(nèi)部類(lèi),當(dāng)然,其作用絕不僅僅這么一點(diǎn)(關(guān)于Lambda的具體使用,讀者可以參考java8函數(shù)式編程這本書(shū),作者解析的很詳細(xì))。接下來(lái)我們先來(lái)看一下java8關(guān)于接口的的變動(dòng)。
Java8中接口的變化
其實(shí)Java9中關(guān)于接口,又有了進(jìn)一步的變動(dòng),這里我們暫且局限于Java8。在Java8中,接口可以包含靜態(tài)方法,另外還增加了一個(gè)用于修飾方法的關(guān)鍵字--default,稱(chēng)之為默認(rèn)方法(帶有方法體)。
- 靜態(tài)方法
其實(shí)Java8中增加靜態(tài)方法,目的完全出于編寫(xiě)類(lèi)庫(kù),對(duì)某些行為進(jìn)行抽象(還記得我們之前用類(lèi)去做嗎?)。但是有一點(diǎn)不同的是:類(lèi)中的靜態(tài)方法可以繼承,并且可以從實(shí)例獲得引用(并不建議這么做);但是接口中的靜態(tài)方法不能被繼承。
- 默認(rèn)方法
其實(shí),引入默認(rèn)方法,是不得已而為之,因?yàn)镴ava8引入了函數(shù)式接口,許多像Collection這樣的基礎(chǔ)接口中增加了方法,如果還是一個(gè)傳統(tǒng)的抽象方法的話,那么可能很多第三方類(lèi)庫(kù)就會(huì)變得完全無(wú)法使用。為了實(shí)現(xiàn)二進(jìn)制的向后兼容性,引入了帶有方法體、被default修飾的方法--默認(rèn)方法。其主要思想就是如果子類(lèi)中沒(méi)有實(shí)現(xiàn),那么采用父類(lèi)提供的默認(rèn)實(shí)現(xiàn)。其具體的繼承規(guī)則如圖1-2所示。

圖1-2
其中Parent接口中定義了默認(rèn)方法welcome;
Child接口對(duì)默認(rèn)方法進(jìn)行了覆蓋;
ParentImpl繼承了Parent接口的方法;
ChildImpl繼承了Child的方法;
OverridingParent覆蓋了父類(lèi)的welcome;
OverridingChild最終的welcome來(lái)自于OverridingParent。
關(guān)于繼承規(guī)則,可以簡(jiǎn)短描述為:類(lèi)勝于接口;子類(lèi)勝于父類(lèi);如果前兩者都不適用,那么子類(lèi)要么實(shí)現(xiàn)該方法,要么將該方法聲明為抽象方法。
函數(shù)式接口
關(guān)于接口的變動(dòng),Java8中新定義了一種接口類(lèi)型,函數(shù)式接口,與其他接口的區(qū)別就是:
- 函數(shù)式接口中只能有一個(gè)抽象方法(我們?cè)谶@里不包括與Object的方法重名的方法);
- 可以有從Object繼承過(guò)來(lái)的抽象方法,因?yàn)樗蓄?lèi)的最終父類(lèi)都是Object;
- 接口中唯一抽象方法的命名并不重要,因?yàn)楹瘮?shù)式接口就是對(duì)某一行為進(jìn)行抽象,主要目的就是支持Lambda表達(dá)式。
Java8之前已經(jīng)存在的函數(shù)式接口有:
java.lang.Runnable
java.util.concurrent.Callable
java.security.PrivilegedAction
java.util.Comparator
java.io.FileFilter
java.nio.file.PathMatcher
java.lang.reflect.InvocationHandler
java.beans.PropertyChangeListener
java.awt.event.ActionListener
javax.swing.event.ChangeListener
另外,Java8還提供了@FunctionalInterface注解來(lái)幫助我們標(biāo)識(shí)函數(shù)式接口。另外需要注意的是函數(shù)式接口的目的是對(duì)某一個(gè)行為進(jìn)行封裝,某些接口可能只是巧合符合函數(shù)式接口的定義。
如圖1-3所示,為java8的Function包的結(jié)構(gòu)(即新引入的函數(shù)式接口),圖中綠色表示主要引入的新接口,其他接口基本上都是為了支持基本類(lèi)型而添加的接口,方法的具體作用圖中有具體說(shuō)明。
[圖片上傳失敗...(image-7baf51-1537434242538)]
圖1-3
自定義函數(shù)式接口支持Lambda表達(dá)式
看下如下代碼,最終輸出應(yīng)該是兩行"Hello World!",是不是很神奇?
public class Main {
public static void main(String[] args) {
Action action = System.out :: println;
action.execute("Hello World!");
test(System.out :: println, "Hello World!");
}
static void test(Action action, String str) {
action.execute(str);
}
}
@FunctionalInterface
interface Action<T> {
public void execute(T t);
}
小結(jié)
本文對(duì)Lambda以及函數(shù)式接口進(jìn)行了簡(jiǎn)要介紹,目的是激發(fā)大家使用Lambda的興趣,步入函數(shù)式編程的大門(mén)。