kotlin—lambda及其原理

1、lambda簡(jiǎn)介

lambda表達(dá)式是函數(shù)字面詞残黑,首先它是一個(gè)表達(dá)式馍佑,此表達(dá)式的結(jié)果是返回一個(gè)函數(shù)而且函數(shù)是未實(shí)現(xiàn)聲明的,可以理解為lambda表達(dá)式聲明了一個(gè)函數(shù)同時(shí)將此函數(shù)作為表達(dá)式的結(jié)果返回給調(diào)用者梨水。
lambda表達(dá)在kotlin中廣泛使用拭荤,因?yàn)槠涫褂煤?jiǎn)單明了。

2疫诽、lambda語(yǔ)法

lambda表達(dá)式的完整語(yǔ)法如下:

val 變量 : ([參數(shù)類(lèi)型列表]) -> 返回類(lèi)型 = { [參數(shù)名: 參數(shù)類(lèi)型[, 參數(shù)列表.....]] -> 方法體 }

等號(hào)后面花括號(hào){}的內(nèi)容就是lambda表達(dá)式舅世,語(yǔ)法規(guī)則可以總結(jié)如下:

  • lambda表達(dá)式是總是由花括號(hào){}包裹
  • lambda表達(dá)式聲明的函數(shù)參數(shù)列表在 -> 前面,與普通函數(shù)參數(shù)聲明方式一樣奇徒,但:
    • 參數(shù)列表不能用括號(hào)()包裹
    • 沒(méi)有任何參數(shù)時(shí)雏亚,則不需要括號(hào)和->
    • 只有一個(gè)參數(shù)時(shí),則可以省略參數(shù)使用默認(rèn)的it代替摩钙,同時(shí)省略->
  • lambda表達(dá)式聲明的函數(shù)體在 -> 后面
  • lambda表達(dá)式聲明的函數(shù)的返回值不是Unit罢低,則最后一個(gè)表達(dá)式作為函數(shù)的返回值,如果

2.1 拖尾lambda表達(dá)式

拖尾lambda表達(dá)式就是如果函數(shù)的最后一個(gè)參數(shù)是函數(shù)類(lèi)型胖笛,那么作為以lambda表達(dá)式函數(shù)參數(shù)可以提放到括號(hào)()之外:

val sum = items.sum(1) { a, b -> a + b }

其等價(jià)于:

val sum = items.sum(1, {a, b -> a + b})

如果lambda表達(dá)式是函數(shù)的唯一一個(gè)參數(shù)网持,則函數(shù)的參數(shù)及括號(hào)可以省略,如下所示:

val sum = items.sum { a, b -> a + b }

其等價(jià)于:

val sum = items.sum({a, b -> a + b})

2.2 lambda表達(dá)式隱匿的參數(shù)it

如果lambda表達(dá)式的函數(shù)參數(shù)列表只有一個(gè)长踊,則可以省略函數(shù)參數(shù)列表功舀,該參數(shù)使用隱匿的it表示,如下所示:

val sum = items.sum { it + 1 }

其等價(jià)于:

val sum = items.sum {it -> it + 1}

2.3 lambda表達(dá)式返回值

lambda表達(dá)式中的函數(shù)體中的最后一個(gè)表達(dá)式默認(rèn)作為lambda表達(dá)式的返回值之斯,也可以顯示返回一個(gè)值:

val sum: (Int, Int) -> Int = { a, b ->
        a + b
    }

等價(jià)于:

val sum: (Int, Int) -> Int = tag@{ a, b ->
        return@tag a + b
    }

3日杈、lambda原理

上面介紹了lambda表達(dá)式的語(yǔ)法、規(guī)則佑刷,那么lambda的實(shí)現(xiàn)原理是什么呢莉擒?
我們通過(guò)一個(gè)簡(jiǎn)單的例子剖析其原理:

class TestFun {
    val sum: (Int, Int) -> Int = { a, b ->
        a + b
    }

    fun main() {
        sum(1, 2)
    }
}

編譯之后的字節(jié)碼文件之后,會(huì)發(fā)現(xiàn)除了生成TestFun.class文件文件之外瘫絮,還生成了一個(gè)匿名類(lèi)TestFunsum1涨冀,其實(shí)它是lambda表達(dá)式轉(zhuǎn)成的一個(gè)匿名類(lèi),我們使用javac -c -v -p xxx.class反編譯TestFunsum1得到的偽代碼為:

//TestFun$sum$1繼承Lambda 類(lèi)麦萤,同時(shí)實(shí)現(xiàn)Function2接口
final class com.wyx.tcanvas.test.delegate.TestFun$sum$1 extends kotlin.jvm.internal.Lambda implements kotlin.jvm.functions.Function2<java.lang.Integer, java.lang.Integer, java.lang.Integer>
  //省略......
{
  //定義TestFun$sum$1的單例對(duì)象
  public static final com.wyx.tcanvas.test.delegate.TestFun$sum$1 INSTANCE;
    descriptor: Lcom/wyx/tcanvas/test/delegate/TestFun$sum$1;
    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
  //構(gòu)造函數(shù)
  com.wyx.tcanvas.test.delegate.TestFun$sum$1();
    descriptor: ()V
    flags: (0x0000)
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: iconst_2
         //調(diào)用父類(lèi)lambda的init方法
         2: invokespecial #12                 // Method kotlin/jvm/internal/Lambda."<init>":(I)V
         5: return
       //省略......
  
  //lambda表達(dá)函數(shù)體生成的對(duì)應(yīng)函數(shù)invoke鹿鳖,invode函數(shù)的內(nèi)容就是lambda的函數(shù)體內(nèi)容
  public final java.lang.Integer invoke(int, int);
    descriptor: (II)Ljava/lang/Integer;
    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
    Code:
      stack=2, locals=3, args_size=3
         0: iload_1 //加載第2個(gè)局部變量即函數(shù)參數(shù)的第1個(gè)變量a,到棧頂
         1: iload_2 //加載第3個(gè)局部變量即函數(shù)參數(shù)的第2個(gè)變量b
         2: iadd //對(duì)棧頂兩個(gè)元素進(jìn)行正式相加
         //將相加結(jié)果轉(zhuǎn)為Integer類(lèi)型扁眯,并放到棧頂
         3: invokestatic  #23                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         6: areturn //返回棧頂即相加的結(jié)果
      //省略......
  
  //實(shí)現(xiàn)Function2接口的invoke方法
  public java.lang.Object invoke(java.lang.Object, java.lang.Object);
    descriptor: (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=3, locals=3, args_size=3
         0: aload_0
         1: aload_1
         //校驗(yàn)函數(shù)第1個(gè)參數(shù)是否為Number類(lèi)型
         2: checkcast     #29                 // class java/lang/Number
          //將函數(shù)第1個(gè)參數(shù)轉(zhuǎn)為Integer類(lèi)型
         5: invokevirtual #33                 // Method java/lang/Number.intValue:()I
         8: aload_2
         //校驗(yàn)函數(shù)第2個(gè)參數(shù)是否為Number類(lèi)型
         9: checkcast     #29                 // class java/lang/Number
        //將函數(shù)第2個(gè)參數(shù)轉(zhuǎn)為Integer類(lèi)型
        12: invokevirtual #33                 // Method java/lang/Number.intValue:()I
        //調(diào)用上面的成員方法invoke方法,參數(shù)分別對(duì)應(yīng)此函數(shù)的參數(shù)翅帜,將結(jié)果放到棧頂
        15: invokevirtual #35                 // Method invoke:(II)Ljava/lang/Integer;
        18: areturn //返回棧頂?shù)闹?       //省略......
  
  //匿名類(lèi)的靜態(tài)語(yǔ)句塊姻檀,
  static {};
     //省略......
      //創(chuàng)建TestFun$sum$1的實(shí)例對(duì)象
         0: new           #2                  // class com/wyx/tcanvas/test/delegate/TestFun$sum$1
         3: dup
          //調(diào)用TestFun$sum$1的實(shí)例對(duì)象的init方法
         4: invokespecial #41                 // Method "<init>":()V
          //將創(chuàng)建的TestFun$sum$1的實(shí)例對(duì)象賦值給單例對(duì)象INSTANCE
         7: putstatic     #44                 // Field INSTANCE:Lcom/wyx/tcanvas/test/delegate/TestFun$sum$1;
        10: return
}
 //省略......

我們看到lambda表達(dá)式編譯之后會(huì)通過(guò)生成一個(gè)匿名類(lèi)來(lái)實(shí)現(xiàn),匿名類(lèi)繼承Lambda類(lèi)同時(shí)實(shí)現(xiàn)Function接口(根據(jù)lambda表達(dá)式函數(shù)參數(shù)個(gè)數(shù)實(shí)現(xiàn)對(duì)應(yīng)的Function1/Function2....)涝滴,匿名類(lèi)內(nèi)部在類(lèi)加載時(shí)創(chuàng)建它的一個(gè)公有單例(供外部通過(guò)此單例調(diào)用成員方法)绣版,匿名類(lèi)同時(shí)有兩個(gè)invoke方法,一個(gè)將lambda表達(dá)式轉(zhuǎn)為函數(shù)歼疮,一個(gè)是實(shí)現(xiàn)了Function接口的invoke函數(shù)杂抽,F(xiàn)unction接口的invoke函數(shù)內(nèi)部調(diào)用lambda表達(dá)式轉(zhuǎn)成的函數(shù)invoke。

4韩脏、總結(jié)

lambda表達(dá)式其實(shí)是通過(guò)生成一個(gè)匿名類(lèi)實(shí)現(xiàn)缩麸,它繼承Lambda類(lèi)同時(shí)實(shí)現(xiàn)Function接口(此Function接口根據(jù)lambda表達(dá)式函數(shù)的個(gè)數(shù)選擇不同的Function系列接口,比如只有一個(gè)參數(shù)赡矢,則實(shí)現(xiàn)Function1接口杭朱,依次類(lèi)推)。使用是調(diào)用Function的invoke方法济竹,F(xiàn)unction的invoke方法轉(zhuǎn)而去調(diào)用匿名類(lèi)生成的與lambda表達(dá)式對(duì)應(yīng)的方法invoke痕檬。
lambda表達(dá)式不一定實(shí)現(xiàn)的是Function接口,它會(huì)根據(jù)所處的上下文環(huán)境實(shí)現(xiàn)響應(yīng)的接口送浊,比如用lambda表達(dá)式代替一個(gè)callback梦谜,則它實(shí)現(xiàn)的就不是Function接口了而是callback。
如果lambda僅僅用作一個(gè)匿名函數(shù)袭景,其實(shí)是大財(cái)所用了唁桩,因?yàn)樗鼤?huì)額外的生成一個(gè)匿名類(lèi),使用時(shí)使用此匿名類(lèi)的單例調(diào)用對(duì)應(yīng)的函數(shù)耸棒,不僅增加一個(gè)類(lèi)而且還需要在時(shí)候的是創(chuàng)建它的一個(gè)單例對(duì)象而且還需要兩次的函數(shù)調(diào)用荒澡,不僅增加了空間還損耗了運(yùn)行時(shí)間,所以lambda僅僅用作一個(gè)匿名函數(shù)是不明智的選擇与殃,此場(chǎng)景建議使用一個(gè)真實(shí)的具名成員函數(shù)較好单山。
lambda用在類(lèi)似callback、方法參數(shù)較為合適幅疼,因?yàn)榉凑夹枰梢粋€(gè)匿名類(lèi)米奸,所以性能是差不多的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末爽篷,一起剝皮案震驚了整個(gè)濱河市悴晰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖铡溪,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件漂辐,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡棕硫,警方通過(guò)查閱死者的電腦和手機(jī)髓涯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)饲帅,“玉大人复凳,你說(shuō)我怎么就攤上這事瘤泪≡畋茫” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵对途,是天一觀的道長(zhǎng)赦邻。 經(jīng)常有香客問(wèn)我,道長(zhǎng)实檀,這世上最難降的妖魔是什么惶洲? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮膳犹,結(jié)果婚禮上恬吕,老公的妹妹穿的比我還像新娘。我一直安慰自己须床,他們只是感情好铐料,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著豺旬,像睡著了一般钠惩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上族阅,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天篓跛,我揣著相機(jī)與錄音怒医,去河邊找鬼观游。 笑死厂捞,一個(gè)胖子當(dāng)著我的面吹牛殉疼,可吹牛的內(nèi)容都是我干的野瘦。 我是一名探鬼主播涂屁,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼涩惑,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼射窒!你這毒婦竟也來(lái)了渴频?” 一聲冷哼從身側(cè)響起芽丹,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎卜朗,沒(méi)想到半個(gè)月后拔第,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體咕村,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年蚊俺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了懈涛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡泳猬,死狀恐怖批钠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情得封,我是刑警寧澤埋心,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站忙上,受9級(jí)特大地震影響拷呆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜疫粥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一茬斧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧梗逮,春花似錦项秉、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至瞬欧,卻和暖如春贷屎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背艘虎。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工唉侄, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人野建。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓属划,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親候生。 傳聞我的和親對(duì)象是個(gè)殘疾皇子同眯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容