參考資料:
https://mp.weixin.qq.com/s/ht3KIeG9-H9HtgF_nlm5cw
Lambda,中文名“蘭布達(dá)”。是匿名函數(shù)的別名,Java8后開(kāi)始引入Lambda表達(dá)式.而Android方面Android Studio 2.4 Preview 4 及其之后完全的支持lambda 表達(dá)式,如果是之前版本就需要借助插件和編譯器了。下面以我們常見(jiàn)的點(diǎn)擊事件為例開(kāi)始講解Lambda表達(dá)式,先看下面的代碼:
TextView tv =findViewById(R.id.textView);
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
上面的代碼是我們常見(jiàn)的簡(jiǎn)單的不能在簡(jiǎn)單的點(diǎn)擊事件,我們把此代碼轉(zhuǎn)化為L(zhǎng)ambda表達(dá)是看看效果,等等,我們上面說(shuō)了Java8后開(kāi)始引入Lambda表達(dá)式支持,Android Studio 2.4 Preview 4 及其之后完全的支持lambda 表達(dá)式,那我們只需要設(shè)置一下自己的Project引用的是JDK1.8即可,如圖:

然后我們回到代碼發(fā)現(xiàn),建議開(kāi)發(fā)者替換原有寫(xiě)法,改為lambda表達(dá)式:

我們按照要求選擇一下,然后回車(chē):

既可以看到代碼變成了如下的樣子:

我去,,第一眼感覺(jué)就是代碼變少了。這其實(shí)也是Lambda表達(dá)式的優(yōu)點(diǎn):其對(duì)匿名內(nèi)部類(lèi)笨拙繁瑣的代碼的簡(jiǎn)化.lambda 表達(dá)式不僅對(duì)對(duì)象名進(jìn)行隱匿,更完成了方法名的隱匿,展示了一個(gè)接口抽象方法最有價(jià)值的兩點(diǎn):參數(shù)列表和具體實(shí)現(xiàn).
那么我們就來(lái)探討監(jiān)聽(tīng)事件是怎么通過(guò)Lambda表達(dá)式一步步的如此簡(jiǎn)潔的。
1.Lambda表達(dá)式的形式
Lambda表達(dá)式共有三種形式:函數(shù)式接口,方法引用,構(gòu)造器引用。其中函數(shù)式接口是最基本的,其余兩種形式都是基于它進(jìn)行拓展。
1.1函數(shù)式接口
函數(shù)式接口指的是有且只有一個(gè)抽象方法的接口,比如我們上面的OnClickListener。lambda 表達(dá)式就是對(duì)這類(lèi)接口的匿名內(nèi)部類(lèi)進(jìn)行簡(jiǎn)化。基本形式如下:
( 參數(shù)列表... ) -> { 語(yǔ)句塊... }
- ok,那我們基于基本形式對(duì)setOnClickListener(new View.OnClickListener()){}做一下改變?yōu)椋?/li>
// 基本形式如下:( 參數(shù)列表... ) -> { 語(yǔ)句塊... }
tv.setOnClickListener((View v) -> {
//doSomeThing.....
});
- 當(dāng)參數(shù)只有一個(gè)時(shí),參數(shù)列表兩側(cè)的圓括號(hào)也可省略
//參數(shù)只有一個(gè)時(shí)(注意是只有一個(gè)時(shí),兩個(gè)時(shí)就正常寫(xiě)吧),參數(shù)列表兩側(cè)的圓括號(hào)也可省略
tv.setOnClickListener(v -> {
//doSomeThing.....
});
- 當(dāng)語(yǔ)句塊內(nèi)的處理邏輯只有一句表達(dá)式時(shí),其兩側(cè)的花括號(hào)也可省略
tv.setOnClickListener(v -> Log.e(TAG, "花括號(hào)也可省略" ));
看到?jīng)],就是這個(gè)樣子,就是這么變過(guò)來(lái)的,就是這么簡(jiǎn)單
-
當(dāng)只有一句去除花括號(hào)的表達(dá)式且接口方法需要返回值時(shí),這個(gè)表達(dá)式不用(也不能)在表達(dá)式前加 return ,就可以當(dāng)作返回語(yǔ)句。下面用 Java 的 Function 接口作為示例,這是一個(gè)用于轉(zhuǎn)換類(lèi)型的接口,在這里我們獲取一個(gè) User 對(duì)象的姓名字符串并返回:
image.png
1.2方法引用形式
我們先來(lái)一個(gè)方法引用形式的分類(lèi),然后一個(gè)個(gè)講解,方法應(yīng)用形式有三種:
- ClassName :: staticMethod
- ClassName :: instanceMethod
- object :: instanceMethod
-
1.2.1 ClassName :: staticMethod
直接調(diào)用某類(lèi)的靜態(tài)方法,并將接口方法參數(shù)傳入。
比如我們寫(xiě)一個(gè)例子看看,我們先定義一個(gè)判斷字符是否為空的接口:
public interface IIsEmpty<T> {
//判斷是否為空的接口
boolean isEmpty( T t);
}
我們知道TextUtils.isEmpty()可以判斷非空,那我們就引用,然后在看其具體的實(shí)現(xiàn):
IIsEmpty<String> iIsEmpty = s1 -> TextUtils.isEmpty(s1);
我們?cè)诶^續(xù)使用方法引用的形式繼續(xù)簡(jiǎn)化這一段 lambda 表達(dá)式(也就是 ClassName :: staticMethod):
IIsEmpty<String> iIsEmpty = TextUtils::isEmpty;
方法引用形式就是當(dāng)邏輯實(shí)現(xiàn)只有一句且調(diào)用了已存在的方法進(jìn)行處理( this 和 super 的方法也可包括在內(nèi))時(shí),對(duì)函數(shù)式接口形式的 lambda 表達(dá)式進(jìn)行進(jìn)一步的簡(jiǎn)化。傳入引用方法的參數(shù)就是原接口方法的參數(shù)。
如果上面的你還沒(méi)看懂,我們?cè)趤?lái)一個(gè)例子:
interface Predicate<T> {
boolean test(T t);
}
public static boolean doPredicate(int age, Predicate<Integer> predicate) {
return predicate.test(age);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//判斷輸入的數(shù)字是否大于18
boolean isAdult = doPredicate(20, x -> x >= 18);
System.out.println(isAdult);
}
這下懂了沒(méi)?
實(shí)際上上述例子中的接口是不用我們自己寫(xiě)的,jdk設(shè)計(jì)者已經(jīng)為我們準(zhǔn)備了java.util.function包:

我們前面寫(xiě)的Predicate函數(shù)式接口也是JDK種的一個(gè)實(shí)現(xiàn),他們大致分為以下幾類(lèi):

1.2.2 object :: instanceMethod
直接調(diào)用任意對(duì)象的實(shí)例方法,如 obj::equals 代表調(diào)用 obj 的 equals 方法與接口方法參數(shù)比較是否相等,效果等同 obj.equals(t);。-
1.2.3ClassName :: instanceMethod
較為特殊,將接口方法參數(shù)列表的第一個(gè)參數(shù)作為方法調(diào)用者,其余參數(shù)作為方法參數(shù)。由于此類(lèi)接口較少,故選擇 Java 提供的 BiFunction 接口作為示例,該接口方法接收一個(gè) T1 類(lèi)對(duì)象和一個(gè) T2 類(lèi)對(duì)象,通過(guò)處理后返回 R 類(lèi)對(duì)象:
image.png
1.3構(gòu)造器引用
Lambda 表達(dá)式的第三種形式,其實(shí)和方法引用十分相似,只不過(guò)方法名替換為 new 。其格式為 ClassName :: new。這時(shí)編譯器會(huì)通過(guò)上下文判斷傳入的參數(shù)的類(lèi)型、順序、數(shù)量等,來(lái)調(diào)用適合的構(gòu)造器,返回對(duì)象。
我們來(lái)定義一個(gè)實(shí)體類(lèi)UserBean
public class UserBean {
String userName;
int age;
public UserBean(String userName) {
this.userName = userName;
}
public UserBean(){
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public UserBean(String userName, int age) {
this.userName = userName;
this.age = age;
}
@Override
public String toString() {
return "UserBean{" +
"userName='" + userName + '\'' +
", age=" + age +
'}';
}
}
接下來(lái)定義一個(gè)返回UserBean的接口:
public interface Construction<T> {
T getBean(String name ,Integer age);
}
具體使用:
/**
* 通過(guò)Lamdba構(gòu)造器獲取對(duì)象
*/
void constructorMthod() {
Construction<UserBean> construction = UserBean::new;
UserBean userBean =construction.getBean("張三",15);
System.out.println(userBean.toString());
}
2.其他說(shuō)明
2.1 變量捕獲
在使用匿名內(nèi)部類(lèi)時(shí),若要在內(nèi)部類(lèi)中使用外部變量,則需要將此變量定義為 final 變量。因?yàn)槲覀儾⒉恢浪鶎?shí)現(xiàn)的接口方法何時(shí)會(huì)被調(diào)用,所以通過(guò)設(shè)立 final 來(lái)確保安全。在 lambda 表達(dá)式中,仍然需要遵守這個(gè)標(biāo)準(zhǔn)。
不過(guò)在 Java 8 中,新增了一個(gè) effective final 功能,只要一個(gè)變量沒(méi)有被修改過(guò)引用(基本變量則不能更改變量值),即為實(shí)質(zhì)上的 final 變量,那么不用再在聲明變量時(shí)加上 final 修飾符,也就是說(shuō)我們可以不做任何聲明上的改變即可在 lambda 中使用外部變量,前提是我們以 final 的規(guī)則對(duì)待這個(gè)變量。
2.2 this 關(guān)鍵字
在匿名內(nèi)部類(lèi)中,this 關(guān)鍵字指向的是匿名類(lèi)本身的對(duì)象,而在 lambda 中,this 指向的是 lambda 表達(dá)式的外部類(lèi)。
2.3 方法數(shù)量差異
當(dāng)前 Android Studio 對(duì) Java 8 新特性編譯時(shí)采用脫糖(desugar)處理,lambda 表達(dá)式經(jīng)過(guò)編譯器編譯后,每一個(gè) lambda 表達(dá)式都會(huì)增加 1~2 個(gè)方法數(shù)。而 Android 應(yīng)用的方法數(shù)不能超過(guò) 65536 個(gè)。雖然一般應(yīng)用較難觸發(fā),但仍需注意。
2.4 默認(rèn)方法
在Java語(yǔ)言中,一個(gè)接口中定義的方法必須由實(shí)現(xiàn)類(lèi)提供實(shí)現(xiàn)。但是當(dāng)接口中加入新的API時(shí), 實(shí)現(xiàn)類(lèi)按照約定也要修改實(shí)現(xiàn),而Java8的API對(duì)現(xiàn)有接口也添加了很多方法,比如List接口中添加了sort方法。 如果按照之前的做法,那么所有的實(shí)現(xiàn)類(lèi)都要實(shí)現(xiàn)sort方法,JDK的編寫(xiě)者們一定非常抓狂,那應(yīng)當(dāng)怎么辦呢?
在Java8種引入新的機(jī)制,支持在接口中聲明方法同時(shí)提供實(shí)現(xiàn)。 這令人激動(dòng)不已,你有兩種方式完成 1.在接口內(nèi)聲明靜態(tài)方法 2.指定一個(gè)默認(rèn)方法。
如我們上面定義的Predicate接口:

調(diào)用執(zhí)行 :
Predicate predicate = o -> false;
predicate.testMethod();
基本就到這里了,拜拜。

