Kotlin有5个作用域函数,分别是let{}
,run{}
,with{}
,apply{}
和also{}
,它们都可以接收并且执行一个lamda表达式,去掉lamda表达式的argument列表和{},看上去就像在执行代码块 (KClosure)。抛开Kotlin实现它们的动机和原理,我的目标就是学会怎么用它们!
不知道如何选择?
首先看Kotlin这一部分的guide,发现首先要搞懂下面两个简单的概念:
1️⃣上下文对象 (Context Object): this versus it
2️⃣返回结果 (Result): lamda versus object
初学编程应该都知道C风格语言喜欢用一对{}表示一个作用域 (Scope),也应该清楚lamda表达式返回结果是最后一行代码/表达式的执行结果。
上下文对象就是调用作用域函数的对象,以let为例就是someObject.let{}
里的someObject,而作用域函数内部的lamda表达式要持有someObject的一份引用,那么有两种方式,一是lamda接收对象的this关键字,二是lamda表达式的argument。
原形:
someObject.let {it->}
变体:
someObject.let{}
返回结果要么就是lamda表达式的结果,要么是对象本身。如果是lamda结果,就代表这中间做了一些额外的计算或任务,然后可以赋值给一个局部变量;如果指向对象本身,那就是对象内部成员的配置或操作,然后可以继续调用对象方法或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文档的建议
- 在非空对象执行lamda:
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")
- 在对像上调用一系列业务函数:
val zhangsan = Person()
with(zhangsan) {
doQiangjian(zhangsan) // 张三强X妇女
doFeifajizi(zhangsan) // 张三非法集资
}
补充:takeIf()
和takeUnless()
接受一个Predicate然后返回null或对象,然后可以组合作用于函数继续链式调用,其中takeUnless表示除......之外。
网友评论