Kotlin 的这几个语法糖的用法都是比较相似的:
letThe context object is available as an argument (it). The return value is the lambda result.
val str: String? = "Hello"
//processNonNullString(str) // compilation error: str can be null
val length = str?.let {
println("let() called on $it")// 'it' refers to 'str: String = "Hello"'
processNonNullString(it) // OK: 'it' is not null inside '?.let { }'
it.length
}
with
A non-extension function:
the context object is passed as an argument,
but inside the lambda, it’s available as a receiver (this). The return value is the lambda result.
val numbers = mutableListOf("one", "two", "three")
val firstAndLast = with(numbers) {
"The first element is ${first()}," + " the last element is ${last()}"
// i.e. this.first() and this.last(). 'this' refers to 'numbers = mutableListOf("one", "two", "three")'
}
println(firstAndLast)
run
The context object is available as a receiver (this). The return value is the lambda result.
val service = MultiportService("https://example.kotlinlang.org", 80)
val result = service.run {
port = 8080
query(prepareRequest() + " to port $port")
// 'this' is elided. 'this' refers to 'service = MultiportService("https://example.kotlinlang.org", 80)'
}
apply
The context object is available as a receiver (this). The return value is the object itself.
val adam = Person("Adam").apply {
age = 32
city = "London" // 'this' is elided. 'this' refers to 'adam = Person("Adam")'
}
also
The context object is available as an argument (it). The return value is the object itself.
val numbers = mutableListOf("one", "two", "three")
numbers
.also { println("The list elements before adding new one: $it") }
.add("four")
// 'it' refers to 'numbers = mutableListOf("one", "two", "three")'
解析
根据自己的需求与偏好,选用合适的 Scope Function
可见,这几个 scope function 一般都是对一个对象(即所谓的 context object)使用的(当然,如果实在想仅仅使用它们而不理会 context object,也不是不可以:编译能过,对执行结果也没有影响,就是没什么必要),而且一般是为了只需要写更少的代码就能引用这个对象或者操作这个对象的成员。那么:
1、到底要选用哪个 scope function,很多时候取决于用户自己(的习惯):
如果想用 it 引用这个对象,就用 let 或 also;如果想用 this 引用这个对象,就用 with 或 apply 或 run。
使用 with 时,需要显式将需要操纵的对象作为一个参数传入,而不是直接将它 “作为这个对象的成员”(仔细观察,使用 let、run、apply 和 also 之前,都引用了这个 context object 并且加了一个成员访问运算符 “.”)。但是在 scope function 内,同样可以使用 this 去引用这个对象。
2、还有另一个因素影响 scope function 的选用。我们知道,访问对象的成员函数时,整个语句的返回值的类型由成员函数决定。例如
val A: ArrayList() A.add(1)
“A.add(1)” 返回的值,是由 add(Int) 这个成员函数决定的。对于如下的例子:
val f: TreeMap() val g = f.apply { put(1, 2) put(3, 4) }
在这里,g 的值,就是 apply {} 这个 scope function 的返回值,也就是 f。换句话说:
如果想让 scope function 返回对象本身,则选用 let,apply 或 also;如果想让 scope function 返回 lambda 结果,则选用 with 或 run。
把这两条规则合起来:
[1] 如果想用 it 引用这个对象,就用 let 或 also;
如果想用 this 引用这个对象,就用 with 或 apply 或 run。
[2] 如果想让 scope function 返回对象本身,则选用 let,apply 或 also;
如果想让 scope function 返回 lambda 结果,则选用 with 或 run。
根据这两条规则,便可选出最适合的那一个 scope function 进行使用。
使用其中一个 scope function 实现的功能,也可以用其它四个分别等效实现。不过,选出最合适的来使用,有利于减少代码长度。
receiver 是函数类型的一部分,用于代表该函数所属的类。例如
class HTML {
fun body() { ... }
}
fun html(init: HTML.() -> Unit): HTML {
val html = HTML() // create the receiver object
html.init() // pass the receiver object to the lambda
return html
}
html { // lambda with receiver begins here
body() // calling a method on the receiver object
}
可以看出,函数 html 的参数是一个 HTML.() -> Unit 型的函数 init,HTML 就是函数类型的 receiver。即:init 是一个类 HTML 的成员函数,它不返回任何值。函数 html 执行的过程中,执行指定的函数 init。
根据 Kotlin 的 lambda 表达式的语法,下面两种写法是等价的:
html { body() }
html(body())
可见,这里调用了一次 html 函数,传入了 HTML 类的成员函数 body() 作为其参数。



