主要內(nèi)容
- Lambda表達(dá)式
- Functional接口
- 方法引用
1.Lambda表達(dá)式
Lambda表達(dá)式這個(gè)新特性也許是Java8最受歡迎的一個(gè)煎饼,以至于在Android Studio還沒有提供正式支持的時(shí)候讹挎,就有開發(fā)出的插件來兼容。
本人最先接觸Lambda表達(dá)式是在Python中,眾所周知Python語言是極其簡潔的筒溃,Lambda表達(dá)式更是為其提供了巨大貢獻(xiàn)马篮。不過第一次接觸的時(shí)候,感覺這種寫法非常奇怪怜奖,但是寫的多了以后浑测,就會(huì)體會(huì)到Lambda表達(dá)式的巨大魅力。
由于本人現(xiàn)在主要是Android開發(fā)歪玲,所以就從Android代碼中舉例迁央。個(gè)人認(rèn)為在Android開發(fā)中有兩個(gè)比較煩人的事情。第一個(gè)是findViewById()滥崩,只要不借用其他手段岖圈,寫一個(gè)界面都要寫大量的findViewById(),費(fèi)事費(fèi)力钙皮,但又少不了蜂科。第二個(gè)就是各種事件的綁定,如:
tv.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
});
可能我就是想顯示一個(gè)Toast短条,或者其他的簡單的點(diǎn)擊事件导匣,就要寫這么一大塊代碼,當(dāng)然你也可以不用匿名內(nèi)部類慌烧,但是隨著整個(gè)類的擴(kuò)大逐抑,以后看個(gè)代碼,跳來跳去的屹蚊,不勝其煩厕氨。
而且這兩個(gè)問題對(duì)于Android初學(xué)者來說,都是一開始都會(huì)遇見的汹粤,估計(jì)學(xué)的時(shí)候也是看的一臉懵逼命斧。(??ˇ?ˇ??)
好在程序員們是永不滿足的,第一個(gè)問題在Android中已經(jīng)有了很好的解決方法嘱兼,第二個(gè)問題也在Java8中得到了妥善解決国葬,解決方法就是我們今天學(xué)習(xí)的Lambda表達(dá)式。
首先官方在文檔中就承認(rèn):
One issue with anonymous classes is that if the implementation of your anonymous class is very simple, such as an interface that contains only one method, then the syntax of anonymous classes may seem unwieldy and unclear.
既然是笨重和不清楚的芹壕,那么Lambda如何來解決呢汇四?為了使不接觸Android開發(fā)的童鞋也能理解,我在這里模擬幾個(gè)類:
interface MyListener {
void doSomething(String name);
}
public class MyButton {
private MyListener listener;
public void setListener(MyListener listener) {
this.listener = listener;
}
public void click(String name){
listener.doSomething(name);
}
}
public static void main(String[] args){
MyButton button = new MyButton();
button.setListener(new MyListener() {
@Override
public void doSomething(String name) {
out.print("hello " + name);
}
});
button.click("jack");
}
先看看setListener這一代碼塊中哪些是多余的踢涌,也就是對(duì)我們的邏輯不重要的通孽。如果不清楚地話,可以回想一下睁壁,如果用的是IntelliJ之類的IDE背苦,使用代碼提示后互捌,IDE自動(dòng)幫我們補(bǔ)全了哪些代碼(什么?你一直用記事本開發(fā)項(xiàng)目行剂,好吧秕噪,我敬你是條好漢,請(qǐng)忽略這些厚宰,它不適用你現(xiàn)在的境界)腌巾。對(duì),當(dāng)你在括號(hào)內(nèi)寫個(gè)new固阁,再寫個(gè)首字母壤躲,回車一敲。OK备燃!除了那行輸出語句碉克,其他的都給你寫了,有的甚至還幫你加一行注釋并齐。
這也就清楚地告訴了我們漏麦,除了那條輸出語句,也就是你自己的邏輯况褪,其他的東西都是千篇一律撕贞,你就是寫出朵花來也還是這些東西。既然IDE可以幫我們做這些東西测垛,那么我們?yōu)楹尾荒軐懞唵我恍┠笈颍切┮粯拥臇|西在編譯的時(shí)候,讓編譯器幫我們補(bǔ)上呢食侮,或者編譯器理解是什么意思就行号涯,補(bǔ)不補(bǔ)都無所謂。于是Lambda表達(dá)就應(yīng)運(yùn)而生锯七。
那么我們看看上面那段代碼用Lambda表達(dá)式怎么寫:
button.setListener( (String name) -> {out.print("hello "+name);} );
對(duì)链快,就是這么簡潔,一行搞定(學(xué)完后面的方法引用時(shí)眉尸,會(huì)更加簡潔)域蜗。
這里就引出了Lambda表達(dá)式的語法:
(Type1 param1, Type2 param2, ..., TypeN paramN) -> {
statment1;
statment2;
//.............
return statmentM;
}
前面是原來方法的參數(shù),中間一個(gè)箭頭噪猾,后面是要執(zhí)行的邏輯霉祸,就這么清晰簡單。
當(dāng)然袱蜡,如果像我剛才舉的例子一樣丝蹭,一行語句,那么花括號(hào)可以省略戒劫,結(jié)尾引號(hào)也可以省略(多于一個(gè)則不可):
button.setListener((String name)->out.print("hello "+name));
有同學(xué)可能會(huì)問半夷,那參數(shù)前面的類型是不是必要的呢?對(duì)迅细,那個(gè)類型巫橄,不是我們使用的時(shí)候所決定的,是在定義的時(shí)候就已經(jīng)決定的茵典,所以在這里就算給寫成另外一種類型也用不了湘换,所以它也是多余的,也可以省略(特別的统阿,如果只有一個(gè)參數(shù)彩倚,外面的那個(gè)小括號(hào)也可以省略,但是如果沒有參數(shù)扶平,必須放一個(gè)空括號(hào))帆离。
button.setListener(name -> out.print("hello " + name))
2.Functional接口
首先需要引入一個(gè)概念--函數(shù)式接口。簡單的來說就是只含一個(gè)方法的普通接口结澄。為什么要只含一個(gè)方法哥谷?目的就是為了更好地支持Lambda表達(dá)式,如果一個(gè)接口中有多個(gè)方法麻献,是不可以使用Lambda表達(dá)式们妥,因?yàn)長ambda表達(dá)式隱去了方法名和參數(shù)類型,無法確認(rèn)到底使用的是哪個(gè)方法勉吻。
更重要的是监婶,對(duì)于現(xiàn)有的能很好支持Lambda表達(dá)式的接口,一旦后期添加方法齿桃,就不再是函數(shù)式接口惑惶,很容易導(dǎo)致大量代碼編譯不過。所以源譬,Java8提供了@FunctionalInterface的注解集惋,來標(biāo)注這個(gè)接口為函數(shù)式接口。對(duì)于添加了該注解的方法踩娘,如果有多個(gè)抽象方法刮刑,將會(huì)直接報(bào)錯(cuò):
注意:雖然函數(shù)式接口只能有一個(gè)抽象方法,但是不影響有默認(rèn)方法和靜態(tài)方法养渴,應(yīng)為這兩種方法不是默認(rèn)重寫的雷绢,在Lambda表達(dá)式中應(yīng)用是不會(huì)出錯(cuò)的:
作為對(duì)應(yīng)的更新,Java8也為我們提供了大量現(xiàn)成的函數(shù)式接口方便使用理卑,他們大多數(shù)邏輯都比較簡單翘紊,大家就算不使用在需要的時(shí)候也可以自己寫出來。
可以參考這里:Java8 函數(shù)式接口
3.方法引用
Java8是將Lambda表達(dá)式作為更新的一個(gè)重頭戲藐唠,所以為我們提供的當(dāng)然不僅僅只是上面的特性帆疟,更是提供了方法引用這種語法鹉究,將Lambda表達(dá)式的簡潔發(fā)揮到了極致。
在介紹什么是方法引用時(shí)踪宠,我們先看一個(gè)例子自赔。前面我提供的第一個(gè)Lambda表達(dá)式后說,下面這個(gè)語句可以更加簡潔:
button.setListener( (String name) -> {out.print("hello "+name);} );
是時(shí)候兌現(xiàn)承諾了:
button.setListener(out::println);
是不是更加簡(sang)潔(xin)明(bing)了(kuang)柳琢,感覺這次Java8誓要將簡潔這條路走到底啊绍妨。
廢話少說,這就是方法引用的一種形式--引用靜態(tài)方法柬脸。它借鑒了類似c++中的操作符::他去,意思是相似的,表示操作符后面的方法是屬于誰的倒堕。
為什么要添加方法引用這種語法呢灾测?可以這樣想,原來Lambda表達(dá)式的箭頭左邊是方法的參數(shù)垦巴,右邊是代碼邏輯行施,為了減少代碼臃腫和提高復(fù)用,我們往往會(huì)專門寫一個(gè)方法魂那,然后傳入左邊的參數(shù)執(zhí)行即可蛾号。也就是類似下面形式:
((arg1,arg2,...) -> fun(arg1,arg2涯雅,...) )
這里的關(guān)鍵就是那個(gè)函數(shù)鲜结,參數(shù)不是我們所能改變的,而我們?cè)诖怂龅膬H僅是將參數(shù)傳入某個(gè)方法活逆,某種意義來說精刷,這也是一種多余的操作,能不能我們?cè)谶@里指定要用的是哪個(gè)方法蔗候,編譯器幫我們完成這個(gè)動(dòng)作呢怒允?
答案當(dāng)然是可以的,利用的就是方法引用這一新語法锈遥。
方法引用一共有下面四種形式:
類型 | 示例 |
---|---|
引用靜態(tài)方法 | ContainingClass::staticMethodName |
引用某個(gè)對(duì)象的實(shí)例方法 | containingObject::instanceMethodName |
引用某種類型的任意對(duì)象的實(shí)例方法 | ContainingType::methodName |
引用構(gòu)造方法 | ClassName::new |
我們由此可以歸納出方法引用的規(guī)律纫事,::操作符后面是方法名,方法名不帶括號(hào)所灸,雖說有四種形式丽惶,但是大體都是符合Java語法的。比如靜態(tài)方法由類名調(diào)用爬立,實(shí)例方法由實(shí)例化對(duì)象調(diào)用钾唬,只是將以前的點(diǎn)號(hào)替換為雙冒號(hào)而已。
同時(shí)要注意俐筋,Lambda表達(dá)式左邊的參數(shù)要和所引用的方法的參數(shù)要一致娘侍,不能多也不能少,順序也要一致驶冒。
方法引用這種語法只有一個(gè)用途儒士,就是用在Lambda表達(dá)式伍绳,以增加代碼的簡潔,如官方文檔所說:
In those cases, it's often clearer to refer to the existing method by name. Method references enable you to do this; they are compact, easy-to-read lambda expressions for methods that already have a name.
4.小結(jié)
在Android Studio沒有正式支持Lambda表達(dá)式時(shí)乍桂,就已經(jīng)可以將我們寫的代碼以Lambda表達(dá)式的形式展現(xiàn),大家就可以感受到Lambda表達(dá)式帶來的便利效床。
不過Lambda表達(dá)式表達(dá)式雖然非常簡潔睹酌,但是對(duì)代碼不熟悉的人,初次看見還是增加了不少閱讀難度(其省略了類名剩檀,抽象方法名憋沿,參數(shù)類型,甚至改變的參數(shù)名沪猴,而Java非常提倡使用一些有清晰語義的命名辐啄,這些命名上的提示對(duì)于代碼閱讀和理解會(huì)有很大幫助)。
但是一旦熟練后你的代碼將變得非常整齊簡潔运嗜,無論是以后的修改還是別人閱覽都會(huì)提供很多便利壶辜。(尤其是在Android開發(fā)中使用RxJava一類的API,其中夾雜著大量匿名內(nèi)部類担租,Lambda表達(dá)式的引入更是讓本來就很清晰的代碼邏輯更加完美)