本文通過代碼實(shí)例詳細(xì)解釋了Lambda表達(dá)式的基本原理,主要包括以下內(nèi)容
- 基本語法
- 函數(shù)的概念
- 函數(shù)式接口
- 方法引用
- Lambda表達(dá)式的優(yōu)點(diǎn)
- 總結(jié)
本文面向有Java編程基礎(chǔ),但是對 Lambda表達(dá)式不熟悉的程序員。
基本語法
讓我們先來看一個簡單的例子
(x, y) -> x + y
上面這個看起來不像java語句的表達(dá)式就是一個簡單的lambda表達(dá)式。
從Java8開始,可以用lambda表達(dá)式來代入到函數(shù)接口的變量中去。函數(shù)接口是只有一個需要實(shí)現(xiàn)的方法的接口顿仇,后續(xù)會詳細(xì)解釋。lambda表達(dá)式基本語法如下:
( 參數(shù) ) -> { 処理 }
其中->稱為lambda運(yùn)算符摆马,它的左邊是參數(shù)列表臼闻,右邊是一系列的處理以及返回值等。
參數(shù)部分的示例
() -> { 處理 } // 參數(shù)0個
(str) -> { 處理 ] // 參數(shù)1個
str -> { 處理 } // 參數(shù)1個的時候可以省略()
(str, n) -> { 處理 } // 參數(shù)2個
(String str, int n) -> { 處理 } // 也可以寫上具體的參數(shù)類型
處理部分的示例
( 參數(shù) ) -> System.out.println(str) //只有一句的時候可以省略return
( 參數(shù) ) -> {
System.out.println(str); // 復(fù)數(shù)個語句的時候用{}括起
return n; // 需要返回值的時候使用return
}
lambda表達(dá)式由參數(shù)和返回值構(gòu)成囤采,聽起來有點(diǎn)像方法述呐。實(shí)際上lambda表達(dá)式在本質(zhì)上與方法是有點(diǎn)類似的。上面例子里的lambda表達(dá)式表示的是這樣的方法:它的參數(shù)是整數(shù)型的x,y,然后它的處理是將x與y相加的和作為返回值返回蕉毯,用方法來類比的話乓搬,有點(diǎn)類似以下的方法
class SomeClass{
public int sum(intx,inty){
return x+y;
}
}
為什么說有點(diǎn)類似方法呢思犁,因?yàn)閘ambda表達(dá)式嚴(yán)格來說表示的一個函數(shù)的對象。
函數(shù)的概念
在前一節(jié)中进肯,我們引出一個函數(shù)的概念激蹲。在Java語言中,一個接受一些參數(shù)江掩,執(zhí)行一些操作并返回某種值的語句塊稱為方法学辱,而在其他一些編程語言中,這樣的語句塊稱為函數(shù)环形。從Java8開始策泣,Java語言也引入了函數(shù)的概念,雖然函數(shù)≈方法抬吟,但是在細(xì)節(jié)上有一些細(xì)微的差別萨咕。
Java語言中的方法是類的成員,在類的定義中編寫方法的定義火本。然后危队,類被實(shí)例化成為對象,方法僅存在于對象之中发侵。打個比方交掏,假設(shè)類的實(shí)例是個西瓜,那方法就像是西瓜里的種子刃鳄,被西瓜包圍盅弛。
而函數(shù)略有不同,因?yàn)楹瘮?shù)本身就是一個對象叔锐。方法被對象包圍挪鹏,而函數(shù)卻直接暴露在外。假如方法是被西瓜包圍的種子,那么函數(shù)就像是把種子從西瓜中取出來愉烙,加工而成的西瓜子讨盒,本身是獨(dú)立存在,并暴露在外的步责。函數(shù)對象的特點(diǎn)是沒有字段(狀態(tài))返顺,只有一個表示行為的一段處理。
Java是典型的面向?qū)ο缶幊陶Z言蔓肯,在編程語言史上還有一類更古老的函數(shù)式編程語言遂鹊。函數(shù)式編程語言將函數(shù)視為第一等公民,換句話說蔗包,將函數(shù)視為對象本身秉扑,可以像普通的值一樣來對待。那么调限,“可以像普通的值一樣來對待“究竟是什么意思呢舟陆,用Java來比喻的話误澳,比如下面這兩條語句:
int i=1
String str="ABC"
看似相似,其實(shí)略有不同秦躯。第一個語句里的1是真正的值忆谓,而第二條語句里的"ABC"實(shí)際上是String類的實(shí)例,這個實(shí)例可以像普通的值一樣對待踱承。函數(shù)也與這個類似陪毡。
在函數(shù)式編程語言中,可以做如下的操作:
- 可以將函數(shù)分配給變量
- 可以將函數(shù)傳遞給函數(shù)的參數(shù)
- 可以返回函數(shù)作為返回值
其中滿足2和3的函數(shù)稱為"高階函數(shù)"(Higher-order Function)。
而Java語言中傳統(tǒng)的方法勾扭,是無法做到以上三點(diǎn)的,這就是方法與函數(shù)最重要的區(qū)別铁瞒。
Java8將函數(shù)式編程語言的優(yōu)點(diǎn)融入到了面向?qū)ο缶幊陶Z言里去妙色,聽起來好像是語言規(guī)范做了重大的變化,但是事實(shí)并非如此慧耍。Java8只是將函數(shù)式編程語言的精髓在概念和語法上進(jìn)行了吸收身辨,而編譯器會在后臺將源代碼進(jìn)行自動轉(zhuǎn)換。
讓我們用傳統(tǒng)的Java來說明一下這一點(diǎn)芍碧。Java5添加了"自動裝箱和自動拆箱"功能煌珊,也就是說基本數(shù)據(jù)類型和相應(yīng)的包裝類類型會自動地相互轉(zhuǎn)換。它看起來像這樣:
int x=10;
Integer y=x;
int z=y;
在概念和語法上泌豆,這看起來確實(shí)是int型的整數(shù)值10自動轉(zhuǎn)換為了Integer類型的對象y定庵。但實(shí)際上,在編譯的時候踪危,編譯器會將源代碼進(jìn)行自動的變換蔬浙,最終變?yōu)槿缦滤镜拇a:
int x=10;
Integer y=new Integer(x);
int z=y.intValue();
也就是說,為了讓程序員更便捷的編碼贞远,Java編譯器一直在幕后努力工作畴博。而Java8里的函數(shù)式編程也是如此。
函數(shù)式接口
Java Lambda表達(dá)式在概念和語法上來講是一個函數(shù)的對象蓝仲,在本質(zhì)上來講的話俱病,它是一個實(shí)現(xiàn)了函數(shù)式接口的匿名類對象的簡潔寫法。
"函數(shù)式接口"(Function Interface)這個名詞聽起來好像很難理解袱结,其實(shí)它是一個非常簡單的概念亮隙。Java8將只包含單個抽象方法聲明的接口稱為函數(shù)式接口,它只是一個人為的規(guī)定擎勘,僅此而已咱揍。
與此同時,Java8添加了一個@FunctionalInterface注解棚饵。這個注解寫在接口定義的前面時煤裙,就表明這個接口是一個函數(shù)式接口掩完,編譯器在編譯時會檢查該接口定義是否符合函數(shù)式接口的要求(只包含單個抽象方法聲明)∨鹋椋基本上你應(yīng)該很少會用到這個注解來定義自己的函數(shù)式接口且蓬,因?yàn)镴ava8提供了大量的泛用的函數(shù)式接口,足夠我們使用了题翰。
在Java8之前Java API中已經(jīng)有好多接口屬于函數(shù)式接口了恶阴。例如,只有一個抽象方法"void run()"的Runnable接口豹障,以及同樣類似的冯事,只有抽象方法"int compare(T o1,T o2)()"的Comparator接口。
這里的重點(diǎn)是"單個抽象方法"血公,沒有或有2個以上抽象方法的接口不是函數(shù)式接口昵仅。那么接口里能夠聲明的明明只有抽象方法,這里為什么會一再強(qiáng)調(diào)抽象方法呢累魔?在Java8之前摔笤,接口上確實(shí)只能聲明抽象方法。但是垦写,Java8的接口規(guī)范發(fā)生了較大的規(guī)范變更吕世。Java8的接口現(xiàn)在可以定義具有static關(guān)鍵字的已實(shí)現(xiàn)的靜態(tài)方法,以及具有default關(guān)鍵字的已實(shí)現(xiàn)的默認(rèn)方法梯投。所以命辖,這里所強(qiáng)調(diào)的抽象方法,是為了與這些已實(shí)現(xiàn)的靜態(tài)方法與默認(rèn)方法作區(qū)別晚伙。另外吮龄,函數(shù)式接口所持有的這種"單個抽象方法",可以用SAM(Single Abstract Method)來簡稱咆疗。
Java8定義了43種函數(shù)式接口漓帚,它們位于java.util.function包里。比如IntBinaryOperator午磁,它的定義如下: