前言
Kotlin标准库的 Standard.kt
包含几个函数,其唯一目的是在对象上下文内执行代码块。 当我们在提供了lambda表达式的对象上调用此类函数时,它将形成一个临时作用域。在此作用域中,我们可以访问没有其名称的对象,这些功能称为作用域函数。
比如我们经常使用的 apply
和 also
函数:
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
public inline fun <T> T.also(block: (T) -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block(this)
return this
}
如果我们仔细比较这两个扩展函数会发现它们非常相似:它们都接收一个 block 函数并返回 this
值。
唯一的差别就在于 apply
的 block 函数类型为:T.() -> Unit
,而 also
的 block 函数类型为:(T) -> Unit)
。
那么这两种函数类型到底差别在哪里呢 ?
T.() -> Unit
和 (T) -> Unit)
首先我们看下
class Test {
fun main(block: Int.() -> Unit) {
val also: Class<Test> = javaClass.also {
val canonicalName = it.canonicalName
println(canonicalName)
}
val apply: Class<Test> = javaClass.apply {
println(canonicalName)
}
}
}
这样看起来不太明显,我们看下AndroidStudio的智能提示:
这样看起来是不是简单多了,在 also
方法中接收到的是 it
, 而在 apply
方法中接收到的是 this
,而表达式 it.canonicalName
和 canonicalName
的差别不用我说了吧。
apply
以 this
值作为接收者调用指定的函数[block],并返回 this
值。
而 also
以 this
值作为参数调用指定的函数[block],并返回 this
值。
参数我们都理解,但是接收者是个什么概念,貌似从来没听过?
Kotlin的函数类型
Kotlin 使用类似 (Int) -> String
的一系列函数类型来处理函数的声明:val onClick: () -> Unit = ……
。
这些类型具有与函数签名相对应的特殊表示法,即它们的参数和返回值:
- 所有函数类型都有一个圆括号括起来的参数类型列表以及一个返回类型:
(A, B) -> C
表示接受类型分别为 A 与 B 两个参数并返回一个 C 类型值的函数类型。 参数类型列表可以为空,如() -> A
。Unit
返回类型不可省略。 - 函数类型可以有一个额外的接收者类型,它在表示法中的点之前指定类型:
A.(B) -> C
表示可以在 A 的接收者对象上以一个 B 类型参数来调用并返回一个 C 类型值的函数。 带有接收者的函数字面值通常与这些类型一起使用。 - 挂起函数属于特殊种类的函数类型,它的表示法中有一个
suspend
修饰符 ,例如suspend () -> Unit
或者suspend A.(B) -> C
。
函数类型表示法可以选择性地包含函数的参数名:(x: Int, y: Int) -> Point
。 这些名称可用于表明参数的含义。
带有接收者的函数类型
带有接收者的函数类型,例如 A.(B) -> C
,可以用特殊形式的函数字面值实例化—— 带有接收者的函数字面值。
Kotlin 提供了调用带有接收者(提供接收者对象)的函数类型实例的能力。
在这样的函数字面值内部,传给调用的接收者对象成为隐式的 this
,以便访问接收者对象的成员而无需任何额外的限定符,亦可使用 this
表达式 访问接收者对象。
这种行为与扩展函数类似,扩展函数也允许在函数体内部访问接收者对象的成员。
为了表示当前的 接收者 我们使用 this 表达式:
- 在类的成员中,this 指的是该类的当前对象。
- 在扩展函数或者带有接收者的函数字面值中,
this
表示在点左侧传递的 接收者 参数。
//Lambda 表达式(函数字面值)
val sum2: Int.(Int) -> Int = { other ->
plus(other) //这里直接调用了Int的plus()方法,也可以写成 this.plus(other)
}
当接收者类型可以从上下文推断时,lambda 表达式可以用作带接收者的函数字面值。