Kotlin 中的 let
是一個 標準庫擴展函數,它廣泛用于作用域函數(Scope Functions)中,尤其適用于對可空對象(nullable)做非空判斷并執行代碼塊的場景。
示例代碼
val name: String? = "123"
name?.let {println(it)
}
這個例子等價于:
if (name != null) {val it = nameprintln(it)
}
也就是說,name?.let { ... }
只有當 name
非空時才執行 let
的 lambda 塊。lambda 表達式中 it
就是 name
的非空值。let
返回 lambda 的返回值。
實現原理
Kotlin 的 let
函數定義在 commonMain/kotlin/util/Standard.kt
中,源碼如下:
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {contract {callsInPlace(block, InvocationKind.EXACTLY_ONCE)}return block(this)
}
它是一個 內聯(inline)函數,在編譯時會被內聯展開,避免 lambda 帶來的性能開銷。泛型 <T, R>
表示接收一個類型為 T
的對象,返回一個類型為 R
的結果。T.let
表示 let
是類型 T
的擴展函數。block: (T) -> R
是接收 T
的函數(lambda 表達式)。也就是說,它只是將當前對象 this
傳入了 block(this)
中。
@kotlin.internal.InlineOnly
,這是一個 注解(Annotation),用于標記某個函數 只能在被 inline(內聯)時使用,否則編譯器會報錯。
比如下面的代碼:
@InlineOnly
inline fun <T> T.let(block: (T) -> Unit): Unit {block(this)
}
表示這個 let
函數 不會生成實際函數調用(它只能內聯展開),避免 Java 或非 Kotlin 編譯器調用這個方法。
為什么要限制只能 inline?為了提高性能,避免生成函數對象和調用開銷,同時 確保代碼安全地被內聯使用,防止其他模塊通過反射或 Java 調用這個方法。
contract {callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
這是Kotlin 的 Contract DSL,用于 給編譯器更多關于 lambda 執行行為的信息,提升智能分析、空安全和優化。表示block
這個 lambda 參數,在函數調用過程中會 被調用且只調用一次(Exactly once)。這使得編譯器可以進行一些靜態分析優化,例如在下面這種空檢查中,判斷 name
非空:
val name: String? = "abc"
name?.let {// 編譯器知道這里 it 一定非空,不會再要求你加 !!println(it.length) // 安全
}
InvocationKind
類型說明:
EXACTLY_ONCE
:block 會被調用且僅一次AT_LEAST_ONCE
:一定會調用一次或多次AT_MOST_ONCE
:最多一次,可能不調用UNKNOWN
:不確定
編譯器依靠這個信息進行控制流分析,提升非空智能推斷、性能優化、檢測死代碼等能力。
R
是泛型返回類型。
inline fun <T, R> T.let(block: (T) -> R): R {return block(this)
}
<T, R>
是泛型聲明,T
是調用 let
的對象類型(接收者),R
是block
函數返回值的類型,也是 let
函數的最終返回值類型。
舉例:
val name = "abc"
val length: Int = name.let { it.length } // block 返回 Int,所以 R = Int
也可以是任意類型:
val upper = "abc".let { it.uppercase() } // R = String
val printResult = "abc".let { println(it) } // R = Unit
編譯后字節碼
比如:
val name: String? = "123"
name?.let {println(it)
}
大致翻譯成 Java 是:
String name = "123";
if (name != null) {System.out.println(name);
}
編譯器把 ?.let { ... }
直接轉成了 if != null
的判斷。lambda 是內聯展開的,不會有額外函數對象生成,所以效率非常高。
常見用途
處理 nullable 類型:
val name: String? = getName()
name?.let {println("非空值是:$it")
}
鏈式調用:
val result = listOf(1, 2, 3).map { it * 2 }.let {it.joinToString()
}
限定作用域變量(避免變量污染):
val userInput = readLine()
userInput?.let {val trimmed = it.trim()println("你輸入的是:$trimmed")
}
// trimmed 在此作用域外不可見