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)TestFun1涨冀,其實(shí)它是lambda表達(dá)式轉(zhuǎn)成的一個(gè)匿名類(lèi),我們使用javac -c -v -p xxx.class反編譯TestFun1得到的偽代碼為:
//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)米奸,所以性能是差不多的。