官方释义作用域函数:
Kotlin 标准库包含几个函数,它们的唯一目的是在对象的上下文中执行代码块。当对一个对象调用这样的函数并提供一个 lambda 表达式时,它会形成一个临时作用域。在此作用域中,可以访问该对象而无需其名称。这些函数称为作用域函数。共有以下五种:let、run、with、apply 以及 also。
当前使用的kotlin版本为1.3.72;
用官方的区别方式,主要从上下文对象引用方式和返回值加以区分:
函数 | 引用方式 | 返回值 |
---|---|---|
run | this | 函数执行结果 |
with | this | 函数执行结果 |
apply | this | 调用者对象 |
also | it | 调用者对象 |
let | it | 函数执行结果 |
引用方式的区别:
简单来说,如果上下文对象被传入lambda函数中当作参数了,则只能使用it了。、
返回值则视源码真正的返回值而定,对于作用域函数而言,要么返回对象本身,要么返回执行结果。
区别从哪里来,自然可以从源码中得出。
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
引用方式:let使用上下文对象this作为lambda表达式参数,使用it引用;
返回值:返回了函数执行结果;
常见场景:1.非空对象执行代码块,2.引入局部变量提高可读性
如:
fun testLet() {
var s: String? = "abc"
s = if (System.currentTimeMillis().rem(2) == 0L) null else s
s?.let { key ->
println(key.length)
}
}
@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
}
引用方式:also使用上下文对象this作为lambda表达式参数,使用it引用;
返回值:返回了调用者(上下文)对象本身;
常见场景:不会改变对象的操作;
如:
fun testAlso() {
var s: String = "abc"
s.also {
println(it.length)
}
.plus("d")
.also {
println(it.length)
}
print(s) //s仍然是abc
}
@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
引用方式:run作为lambda表达式的接收者,使用this引用;
返回值:返回了函数执行结果;
常见场景:1.对象初始化与其他操作并行时,2.作为额外扩展代码块
fun testRun() {
val bytes: IntArray = IntArray(3)
val s = bytes.run {
bytes[0] = 11
bytes[1] = 12
bytes[2] = 13
bytes[0] + bytes[1] + bytes[2]
}
println(s)
}
run有两种调用方式,前者需要使用kotlin.run来调用,后者方可以如其他函数一样使用T.run进行调用;
kotlin.run { }
run{ }
上面两个run函数分别调用前后两种方式,但作用可视为等价——将当前对象作为lambda函数接收者。
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
引用方式:作为lambda表达式的接收者,使用this引用;
返回值:返回了调用者(上下文)对象本身;
常见场景:对象赋值;
如:
fun testApply() {
val s = IntArray(3).apply {
this[0] = 11
this[1] = 12
this[2] = 13
}
println(s.last())
}
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return receiver.block()
}
引用方式:作为lambda表达式的接收者,使用this引用;
返回值:返回了调用者(上下文)对象本身;
with函数的区别在于需要自行传入lambda函数的接收者或调用者;
常见场景:对于某个对象执行某种操作;
如:
fun testWith() {
var s: String = "abc"
with(s) {
for (i in s) {
println(i)
}
}
}
上面的源码中均出现了callsInPlace的调用:
@ContractsDsl public fun <R> callsInPlace(lambda: Function<R>, kind: InvocationKind = InvocationKind.UNKNOWN): CallsInPlace
按源码中的注释,这个函数有两个作用,一是确认当前函数调用的位置是否恰当,二是确认当前函数调用的次数;
其实通篇下来的内容,官方文档都配有详细的解释与举例,只需要结合源码加以理解,这5个函数并不难运用。
最后记忆一下:
函数 | 引用方式 |
---|---|
run | return block(); |
let | return block(this); |
apply | block(); return this; |
also | block(this); return this; |
with | return receiver.block(); |
with最特殊,需要参数;
run最简明,直接返回一个函数结果;
let和run相似,但要将自身作为函数参数传入;
apply在函数执行后,返回了自身对象;
also在自身作为参数的函数执行后,返回了自身对象;
以上。