先來看一個概念。
函數(shù)式接口:函數(shù)式接口(Functional Interface)就是一個有且僅有一個抽象方法,但是可以有多個非抽象方法的接口。
比如java中的Runnable接口就是函數(shù)式接口。
public interface Runnable {
public abstract void run();
}
在項(xiàng)目代碼轉(zhuǎn)為使用kotlin時,會存在和java代碼互相調(diào)用的問題。不知道大家有沒有注意到一個細(xì)節(jié),kotlin構(gòu)建java中的函數(shù)式接口時,可以轉(zhuǎn)為lambda方式。
java方式:
Runnable runnable = new Runnable() {
@Override
public void run() {
}
};
val runnable = Runnable {
print("runnable")
}
可以看到使用lambda表達(dá)式后,對函數(shù)式接口來講,實(shí)現(xiàn)方式更加簡潔。
我們都知道kotlin代碼最終還是會被編譯為字節(jié)碼,那么kotlin中是如何實(shí)現(xiàn)lambda表達(dá)式的呢?
這得分兩種情況:
情況1: lambda表達(dá)式內(nèi)部沒有持有外部的非靜態(tài)變量,方法。
class Lambda{
private var name = "小明" // 外部變量
private fun testLambda(){
var age = 10 // 外部變量
val runnable = Runnable {
print("沒有調(diào)用外部變量或方法") // 沒有調(diào)用age或者name變量
}
}
}
查看testLambda方法編譯后的字節(jié)碼
// 獲取靜態(tài)變量 INSTANCE(該INSTANCE實(shí)際是Runnable對象的一個實(shí)現(xiàn))
GETSTATIC Lambda$testLambda1$runnable$1.INSTANCE : LLambda$testLambda1$runnable$1;
// 檢查 INSTANCE 是否可以轉(zhuǎn)變?yōu)镽unnbale對象,否則拋出異常
CHECKCAST java/lang/Runnable
// INSTANCE推入操作數(shù)棧
ASTORE 1
翻譯成java代碼就會類似于這樣:
public class Lambda {
private String name = "小明";
private static Object INSTANCE = new Runnable() {
@Override
public void run() {
System.out.println("沒有調(diào)用外部類的成員變量");
}
};
private void testLambda1() {
Runnable runnable = (Runnable)INSTANCE;
}
}
可以看到這種情況下lambda表達(dá)式實(shí)際上被轉(zhuǎn)換為靜態(tài)成員變量。
情況二:lambda表達(dá)式內(nèi)部持有外部的非靜態(tài)變量,方法。
class Lambda {
private var name = "小明"
private fun testLambda2() {
val runnable = Runnable {
print(name)
}
}
}
反編譯為java代碼:
private final void testLambda2() {
Runnable runnable = (Runnable)(new Runnable() {
public final void run() {
String var1 = Lambda.this.name;
System.out.print(var1);
}
});
}
可以看到lambda表達(dá)式是采用匿名內(nèi)部類的方式來實(shí)現(xiàn)的。
那么以上兩種方式有什么不同呢?
我們都知道java中匿名內(nèi)部類都會隱式持有外部類的引用(即使不需要引用外部類的變量),當(dāng)匿名內(nèi)部類中有耗時操作時,容易造成內(nèi)存泄露。
像kotlin這樣的實(shí)現(xiàn),在情況一的時候,lambda內(nèi)部沒有持有外部類的引用,不會有任何內(nèi)存泄露的風(fēng)險(xiǎn)。
以上就是kotlin調(diào)用java時,lambda的實(shí)現(xiàn)方式。
kotlin中Functions.kt中定義了從Function0到Function22一共23個接口,每個接口有且僅有一個invoke方法,F(xiàn)unction后邊的數(shù)字代表invoke方法有幾個入?yún)?,?dāng)kotlin中自定義的lambda表達(dá)式在編譯的時候,會被替換為對應(yīng)的接口實(shí)現(xiàn)。
比如:
val lambda: (() -> Unit) = {
}
由于沒有參數(shù),所以實(shí)際上會被編譯為
Function0 lambda = new Function0{
public void invoke(){
}
}
當(dāng)然了,編譯規(guī)則也會像調(diào)用java中的函數(shù)式接口時一樣,僅當(dāng)需要持有this對象時才會編譯為匿名內(nèi)部類。