寫在開頭
Lambda ,希臘字母 “λ” 的英文名稱。沒錯,就是你高中數(shù)學老師口中的那個“蘭布達”棺亭。在編程世界中蝎宇,它是匿名函數(shù)的別名弟劲, Java 從 Java 8 開始引入 lambda 表達式。而 Android 開發(fā)者的世界里姥芥,直到 Android Studio 2.4 Preview 4 及其之后的版本里兔乞,lambda 表達式才得到完全的支持(在此之前需要使用 Jack 編譯器或 retrolambda 等插件,詳見鏈接)。新版本 Android Studio 使用向導詳見 《在 Android Studio 上使用 Java 8 新特性》庸追。
Oracle 官方推出的 lambda 教程開篇第一句就表揚了其對匿名內部類笨拙繁瑣的代碼的簡化霍骄,然而,在各大 RxJava 教程下的評論中淡溯,最受吐槽的就是作者提供的示例代碼用了 lambda 表達式读整,給閱讀造成了很大的障礙。
所以咱娶,在這篇文章中米间,我會先講解 lambda 表達式的作用和三種形式,之后提供一個在 Android Studio 便捷使用 lambda 的小技巧膘侮,然后說一說 lambda 表達式中比較重要的變量捕獲概念屈糊,最后再講一些使用 lambda 表達式前后的差異。
作用
前面提到琼了,lambda 是匿名函數(shù)的別名另玖。簡單來說,lambda 表達式是對匿名內部類的進一步簡化表伦。使用 lambda 表達式的前提是編譯器可以準確的判斷出你需要哪一個匿名內部類的哪一個方法谦去。
我們最經(jīng)常接觸使用匿名內部類的行為是為 view 設置 OnClickListener ,這時你的代碼是這樣的:
button.setOnClickListener(new View.OnClickListener(){
@Override public void onClick(View v){
doSomeWork();
}
});
使用匿名內部類蹦哼,實現(xiàn)了對象名的隱匿鳄哭;而匿名函數(shù),則是對方法名的隱匿纲熏。所以當使用 lambda 表達式實現(xiàn)上述代碼時妆丘,是這樣的:
button.setOnClickListener(
(View v) -> {
doSomeWork();
}
);
看不懂?沒關系,在這兩個示例中局劲,你只要理解勺拣,lambda 表達式不僅對對象名進行隱匿,更完成了方法名的隱匿鱼填,展示了一個接口抽象方法最有價值的兩點:參數(shù)列表和具體實現(xiàn)药有。下面我會對 lambda 的各種形式進行列舉。
形式
在 Java 中苹丸,lambda 表達式共有三種形式:函數(shù)式接口愤惰、方法引用和構造器引用。其中赘理,函數(shù)式接口形式是最基本的 lambda 形式宦言,其余兩種形式都是基于此形式進行拓展。
PS:為了更好的展示使用 lambda 表達式前后的代碼區(qū)別商模,本文將使用 lambda 表達式給引用賦值的形式作為實例展示奠旺,而不是常用的直接將 lambda 表達式傳入方法之中蜘澜。同時,舉例也不一定具有實際意義响疚。
函數(shù)式接口
函數(shù)式接口是指有且只有一個抽象方法的接口兼都,比如各種 Listener 接口和 Runnable 接口。lambda 表達式就是對這類接口的匿名內部類進行簡化稽寒“绫蹋基本形式如下:
( 參數(shù)列表... ) -> { 語句塊... }
下面以 Java 提供的 Comparator 接口來展示一個實例,該接口常用于排序比較:
interface Comparator<T> {int compare(T var1, T var2);}
Comparator<String> comparator = new Comparator<String> (){
@Override public int compare(String s1, String s2) {
doSomeWork();
return result;
}
};
Comparator<String> comparator = (String s1, String s2) -> {
doSomeWork();
return result;
};
當編譯器可以推導出具體的參數(shù)類型時杏糙,我們可以從參數(shù)列表中忽略參數(shù)類型慎王,那么上面的代碼就變成了:
Comparator<String> comparator = ( s1 , s2 ) -> {
doSomeWork();
return result;
};
當參數(shù)只有一個時,參數(shù)列表兩側的圓括號也可省略宏侍,比如 OnClickListener 接口可寫成 :
interface OnClickListener { void onClick(View v); }
OnClickListener listener = v -> { 語句塊... } ;
然而赖淤,當方法沒有傳入?yún)?shù)的時候,則記得提供一對空括號假裝自己是參數(shù)列表(霧)谅河,比如 Runnable 接口:
interface Runnable { void run(); }
Runnable runnable = () -> { 語句塊... } ;
當語句塊內的處理邏輯只有一句表達式時咱旱,其兩側的花括號也可省略,特別注意這句處理邏輯表達式后面也不帶分號绷耍。比如這個關閉 activity 的點擊方法:
button.setOnClickListener( v -> activity.finish() );
同時吐限,當只有一句去除花括號的表達式且接口方法需要返回值時,這個表達式不用(也不能)在表達式前加 return 褂始,就可以當作返回語句诸典。下面用 Java 的 Function 接口作為示例,這是一個用于轉換類型的接口崎苗,在這里我們獲取一個 User 對象的姓名字符串并返回:
interface Function <T, R> { R apply(T t); }
Function <User, String> function = new Function <User, String>(){
@Override public String apply(User user) {
return user.getName();
}
};
Function <User, String> function = user -> user.getName() ;
方法引用
在介紹第一種形式的之前狐粱,我曾寫道:函數(shù)式接口形式是最基本的 lambda 表達式形式,其余形式都是由其拓展而來胆数。那么肌蜻,現(xiàn)在來介紹第二種形式:方法引用形式。
當我們使用第一種 lambda 表達式的時候必尼,進行邏輯實現(xiàn)的時候我們既可以自己實現(xiàn)一系列處理蒋搜,也可以直接調用已經(jīng)存在的方法,下面以 Java 的 Predicate 接口作為示例胰伍,此接口用來實現(xiàn)判斷功能齿诞,我們來對字符串進行全面的判空操作:
interface Predicate<T> { boolean test(T t); }
Predicate<String> predicate=
s -> {
//用基本代碼組合進行判斷
return s==null || s.length()==0 ;
};
我們知道,TextUtils 的 isEmpty() 方法實現(xiàn)了上述功能骂租,所以我們可以寫作:
Predicate<String> predicate = s -> TextUtils.isEmpty(s) ;
這時我們調用了已存在的方法來進行邏輯判斷,我們就可以使用方法引用的形式繼續(xù)簡化這一段 lambda 表達式:
Predicate<String> predicate = TextUtils::isEmpty ;
驚不驚喜斑司?意不意外渗饮?
方法引用形式就是當邏輯實現(xiàn)只有一句且調用了已存在的方法進行處理( this 和 super 的方法也可包括在內)時但汞,對函數(shù)式接口形式的 lambda 表達式進行進一步的簡化。傳入引用方法的參數(shù)就是原接口方法的參數(shù)互站。
接下來總結一下方法引用形式的三種格式:
object :: instanceMethod
直接調用任意對象的實例方法私蕾,如 obj::equals 代表調用 obj 的 equals 方法與接口方法參數(shù)比較是否相等,效果等同obj.equals(t);
胡桃。
當前類的方法可用this::method
進行調用踩叭,父類方法同理。
ClassName :: staticMethod
直接調用某類的靜態(tài)方法翠胰,并將接口方法參數(shù)傳入容贝,如上述TextUtils::isEmpty
,效果等同TextUtils.isEmpty(s);
ClassName :: instanceMethod
較為特殊,將接口方法參數(shù)列表的第一個參數(shù)作為方法調用者之景,其余參數(shù)作為方法參數(shù)斤富。由于此類接口較少,故選擇 Java 提供的 BiFunction 接口作為示例锻狗,該接口方法接收一個 T1 類對象和一個 T2 類對象满力,通過處理后返回 R 類對象:
interface BiFunction<T1, T2, R> {
R apply(T1 t1, T2 t2);
}
BiFunction<String,String,Boolean> biFunction=
new BiFunction<String, String, Boolean>() {
@Override public Boolean apply(String s1, String s2){
return s1.equals(s2);
}
};
// ClassName 為接口方法的第一個參數(shù)的類名,同時利用接口方法的第一個參數(shù)作為方法調用者轻纪,其余參數(shù)作為方法參數(shù)油额,實現(xiàn) s1.equals(s2);
BiFunction<String,String,Boolean> biFunction= String::equals;
構造器引用
Lambda 表達式的第三種形式,其實和方法引用十分相似刻帚,只不過方法名替換為 new 悔耘。其格式為 ClassName :: new
。這時編譯器會通過上下文判斷傳入的參數(shù)的類型我擂、順序衬以、數(shù)量等,來調用適合的構造器校摩,返回對象看峻。
使用技巧
Android Studio 會在可以轉化為 lambda 表達式的代碼上進行如圖的灰色標識,這時將光標移至灰色區(qū)域衙吩,按下 Alt + Enter 互妓,選擇第一項(方法引用和構造器引用在第二項),IDE 就會自動進行轉換坤塞。
變量捕獲
在使用匿名內部類時冯勉,若要在內部類中使用外部變量,則需要將此變量定義為 final 變量摹芙。因為我們并不知道所實現(xiàn)的接口方法何時會被調用灼狰,所以通過設立 final 來確保安全。在 lambda 表達式中浮禾,仍然需要遵守這個標準交胚。
不過在 Java 8 中份汗,新增了一個 effective final 功能,只要一個變量沒有被修改過引用(基本變量則不能更改變量值)蝴簇,即為實質上的 final 變量杯活,那么不用再在聲明變量時加上 final 修飾符。接下來還是通過一個示例解釋熬词,示例中共有三句被注釋掉的賦值語句旁钧,去除任意一句的注釋,都會報錯:Variable used in lambda expression should be final or effectively final互拾。
int effectiveFinalInt=666;//外部變量
//①effectiveFinalInt=233歪今;
button.setOnClickListener(v -> {
Toast.makeText( effectiveFinalInt + "").show();
//②effectiveFinalInt=233;
});
//③effectiveFinalInt=233;
可以看到,我們可以不做任何聲明上的改變即可在 lambda 中使用外部變量摩幔,前提是我們以 final 的規(guī)則對待這個變量彤委。
一點玄學
this 關鍵字
在匿名內部類中,this 關鍵字指向的是匿名類本身的對象或衡,而在 lambda 中焦影,this 指向的是 lambda 表達式的外部類。
方法數(shù)差異
當前 Android Studio 對 Java 8 新特性編譯時采用脫糖(desugar)處理封断,lambda 表達式經(jīng)過編譯器編譯后斯辰,每一個 lambda 表達式都會增加 1~2 個方法數(shù)。而 Android 應用的方法數(shù)不能超過 65536 個坡疼。雖然一般應用較難觸發(fā)彬呻,但仍需注意。
參考資料
書籍:《 Java 核心技術 》
網(wǎng)絡文章:
在 Android Studio 上使用 Java 8 新特性(官方)
Oracle 官方 lambda 教程
匿名函數(shù)--維基百科(需科學上網(wǎng))
深入淺出 Java 8 Lambda 表達式