Kotlin有5個作用域函數,分別是let{},run{},with{},apply{}和also{},它們都可以接收并且執(zhí)行一個lamda表達式,去掉lamda表達式的argument列表和{},看上去就像在執(zhí)行代碼塊 (KClosure)。拋開Kotlin實現它們的動機和原理,我的目標就是學會怎么用它們!
不知道如何選擇?
首先看Kotlin這一部分的guide,發(fā)現首先要搞懂下面兩個簡單的概念:
1??上下文對象 (Context Object): this versus it
2??返回結果 (Result): lamda versus object
初學編程應該都知道C風格語言喜歡用一對{}表示一個作用域 (Scope),也應該清楚lamda表達式返回結果是最后一行代碼/表達式的執(zhí)行結果。
上下文對象就是調用作用域函數的對象,以let為例就是someObject.let{}里的someObject,而作用域函數內部的lamda表達式要持有someObject的一份引用,那么有兩種方式,一是lamda接收對象的this關鍵字,二是lamda表達式的argument。
原形:
someObject.let {it->}
變體:
someObject.let{}
返回結果要么就是lamda表達式的結果,要么是對象本身。如果是lamda結果,就代表這中間做了一些額外的計算或任務,然后可以賦值給一個局部變量;如果指向對象本身,那就是對象內部成員的配置或操作,然后可以繼續(xù)調用對象方法或return語句/直接返回。
// lamda結果賦值給firstNum變量
val numbers = mutableListOf("one", "two", "three", "four", "five")
val firstNum = numbers.first().let{firstItem -> if (firstItem.length >= 5) firstItem else "!" + firstItem + "!"}
// adam是一個Person對象
val adam = Person("Adam").apply {
age = 32
city = "London"
}
另外,還需要了解一件事,大多數作用域函數都是inline拓展函數(let不是inlne),極個別情況下也可以是inline單獨函數,這2個極個別的例外就是with和沒有接收對象 (前面沒有someObject.)的run。
fun <T, R> T.let(f: (T) -> R): R = f(this) // let原型
// also原型
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block(this)
return this
}
// run獨立函數
val decimalRegex = run {
val digits = "0-9"
val sign = "+-"
Regex("?[$digits$hexDigits]+")
}
for (match in decimalRegex.findAll("+1234")) {
println(match.value)
}
其他原型看這位簡誘的整理。
最后下表根據這兩個維度對它們進行了分類。
| 上下文 / 結果 | lamda結果 (賦值) | 自己 (返回) |
|---|---|---|
| it (可重命名) | let(拓展) | also(拓展) |
| this (可省略) | run(拓展/單獨函數), with(單獨函數) | apply(拓展) |
最后的最后,可恥地搬運Kotlin文檔的建議
- 在非空對象執(zhí)行l(wèi)amda:
let - 在{}寫lamda:
someList.filter().map{ it.length }.filter { it > 3 }.let{::println} - 設置對象屬性:
Person("張三").apply { type="法外狂徒" } - 設置對象屬性+調用成員方法:
val sum = Pair(1,2).run { operator = "+" compute(x,y,operator) } - 定義局部表達式:
val expression = run { Regex(".+") } - 日志打印或調試之類不修改對象的操作:
val numbers = mutableListOf("one", "two", "three")
numbers.also { println("新增前: $it") }.add("four")
- 在對像上調用一系列業(yè)務函數:
val zhangsan = Person()
with(zhangsan) {
doQiangjian(zhangsan) // 張三強X婦女
doFeifajizi(zhangsan) // 張三非法集資
}
補充:takeIf()和takeUnless()接受一個Predicate然后返回null或對象,然后可以組合作用于函數繼續(xù)鏈式調用,其中takeUnless表示除......之外。