Lambda 表達式在 Android 開發(fā)中的使用

寫在開頭

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ù)互站。

接下來總結一下方法引用形式的三種格式:

  1. object :: instanceMethod
    直接調用任意對象的實例方法私蕾,如 obj::equals 代表調用 obj 的 equals 方法與接口方法參數(shù)比較是否相等,效果等同 obj.equals(t);胡桃。
    當前類的方法可用this::method進行調用踩叭,父類方法同理。

  2. ClassName :: staticMethod
    直接調用某類的靜態(tài)方法翠胰,并將接口方法參數(shù)傳入容贝,如上述 TextUtils::isEmpty ,效果等同 TextUtils.isEmpty(s);

  3. 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 就會自動進行轉換坤塞。


img

變量捕獲

在使用匿名內部類時冯勉,若要在內部類中使用外部變量,則需要將此變量定義為 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 表達式

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末柄瑰,一起剝皮案震驚了整個濱河市闸氮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌教沾,老刑警劉巖蒲跨,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異授翻,居然都是意外死亡或悲,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門堪唐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來巡语,“玉大人,你說我怎么就攤上這事淮菠∧泄” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵兜材,是天一觀的道長理澎。 經(jīng)常有香客問我逞力,道長曙寡,這世上最難降的妖魔是什么糠爬? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮举庶,結果婚禮上执隧,老公的妹妹穿的比我還像新娘。我一直安慰自己户侥,他們只是感情好镀琉,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蕊唐,像睡著了一般屋摔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上替梨,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天钓试,我揣著相機與錄音,去河邊找鬼副瀑。 笑死弓熏,一個胖子當著我的面吹牛,可吹牛的內容都是我干的糠睡。 我是一名探鬼主播挽鞠,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼狈孔!你這毒婦竟也來了信认?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤均抽,失蹤者是張志新(化名)和其女友劉穎嫁赏,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體到忽,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡橄教,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了喘漏。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片护蝶。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖翩迈,靈堂內的尸體忽然破棺而出持灰,到底是詐尸還是另有隱情,我是刑警寧澤负饲,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布堤魁,位于F島的核電站喂链,受9級特大地震影響,放射性物質發(fā)生泄漏妥泉。R本人自食惡果不足惜椭微,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望盲链。 院中可真熱鬧蝇率,春花似錦、人聲如沸刽沾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽侧漓。三九已至锅尘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間布蔗,已是汗流浹背藤违。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留何鸡,地道東北人纺弊。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像骡男,于是被迫代替她去往敵國和親淆游。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內容