前言
熟悉kotlin的同学都知道,kotlin中有一些很好用的函数。比如run
,with
,let
,also
,apply
。我们平时写代码的过程中肯定都有在使用这些函数,但是我们使用的过程中有留意这些函数是怎么实现的吗?或者说我们有平时有去了解过这些函数的区别吗?如果没有了解过,那么不要紧,这篇文章带你去了解一下kotlin中的这几个基本的函数。在讲解这几个函数之前,我们首先去了解一个概念——作用域函数
作用域函数
在Kotlin有这么一类函数,它们的唯一目的是在对象的上下文中执行代码块。当对一个对象调用这样的函数并提供一个lambda 表达式时,它会形成一个临时作用域。在此作用域中,可以访问该对象而无需其名称。这些函数称为作用域函数。共有以下五种:let
、run
、with
、apply
以及 also
。
这些作用域函数的区别主要有以下三部分:
- 是拓展函数,还是普通函数
- 上下文对象是this还是it
- 返回值是自身还是lambda的最后一行
讲完了什么是作用域函数,那么我们下面就开始通过源码就解析这一类函数。
run函数
/**
* 首先是run函数
*/
fun learnRun(){
val name = "write code"
val name2 = run {
val name = "read code"
println(name) //输出read code
name
}
println(name) //输出write code
println(name==name2) //输出true
}
//run函数的源码
//run函数的参数:接收一个函数类型的参数,这个函数的类型->是一个无参函数并且返回一个类型为R的函数
public inline fun <R> run(block: () -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
在这个例子中,在learnRun
函数的内部有一个分隔开的作用域,在这个作用域内部完全包含一个在输出之前的name 变量被重新定义并初始化为 read code的操作实现。
这个作用域函数本身似乎看起来不是很有用。但是需要注意的一点是:它的返回值,是这个作用域内部的最后一个对象。
run函数的特性:无接受者的非扩展函数,返回值为lambda表达式的结果。
with函数
普通函数实际是一个简写封装,适用于调用同一个类的多个方法或者属性场景,这时可以省去类名重复,直接调用类的方法即可。典型使用场景如下:
fun learnWith(){
val person = Person("hui","boy")
with(person){
print(name)
print(sex)
}
}
//with函数的源码
//特别注意!block 参数是一个带接收者的函数类型,T.()->R 里的 this 代表的是自身实例,所以可以省
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return receiver.block()
}
注意:Kotlin 提供了调用带有接收者(提供接收者对象)的函数类型实例的能力,如上面的T.() -> R。在这样的函数字面值内部,传给调用的接收者对象成为隐式的this,以便访问接收者对象的成员而无需任何额外的限定符,亦可使用 this
表达式访问接收者对象。
with函数的特性:一个非扩展函数,上下文对象作为lambda表达式的接受者,但是在 lambda 表达式内部,它可以作为接收者(this
)使用。 返回值是 lambda 表达式结果。
T.let函数
对于 T.let 函数的声明,你将会注意到 T.let 是传递它自己本身到函数中block: (T) 。将上下文对象作为 lambda 表达式参数。如果没有指定参数名,对象可以用隐式默认名称 it
访问。it
比 this
简短,带有 it
的表达式通常更容易阅读。然而,当调用对象函数或属性时,不能像 this
这样隐式地访问对象。必须使用it.这种调用方式去访问对应的属性。
fun learnLet(){
var person:Person? = Person("hui","boy")
person?.let {
print(it.name)
print(it.sex)
}
}
//T.let源码
//let 是一个 inline 内联扩展函数,扩展任意类型,此处范型表示
//接收一个函数类型参数,函数的参数是这个对象,并且返回一个类型为R的函数
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
//契约
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
//可以看到调用了函数参数的函数,传递参数为调用 let 函数的对象实例,返回值为函数参数调用返回值
return block(this)
}
T.let函数的特性:一个扩展函数,将上下文对象作为 lambda 表达式参数,没有声明lambda参数的时候可以使用it指代上下文对象。 返回值是 lambda 表达式结果。
T.run函数
fun learnTRun(){
var person:Person? = Person("hui","boy")
person?.run {
print(name)
print(sex)
}
}
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
T.run函数与T.let函数非常的像,他们只是参数不同,一个是it类型,一个是this类型
T.run函数的特性:一个扩展函数,将上下文对象作为 lambda 表达式的接受者,在lambda表达式内部,他它可以作为接受者(this
)使用。 返回值是 lambda 表达式结果。
T.also函数
T.also 扩展函数和 T.let 很像,唯一的区别就是返回值不一样,T.let 是以闭包的形式返回函数体内最后一行的值,而 also 扩展函数返回的是传入对象的自身。
public inline fun <T> T.also(block: (T) -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block(this)
//返回调用对象自己
return this
}
T.also函数的特性:一个扩展函数,将上下文对象作为 lambda 表达式参数,没有声明lambda参数的时候可以使用it指代上下文对象。 返回值上下文对象。
T.apply函数
T.apply 扩展函数和 T.run 很像,唯一的区别就是返回值不一样,T.dun 是以闭包的形式返回函数体内最后一行的值,而 T.apply 扩展函数返回的是传入对象的自身。
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
T.apply函数的特性:一个扩展函数,将上下文对象作为 lambda表达式的接受者,但是在 lambda 表达式内部,它可以作为接收者(this
)使用。 返回值上下文对象。
如何选择哪一种参数this还是it
选择this的场景:当你访问接收者对象时你可以省略 this
,来让你的代码更简短。相对地,如果省略了 this
,就很难区分接收者对象的成员及外部对象或函数。因此,对于主要对对象成员进行操作(调用其函数或赋值其属性)的 lambda 表达式,建议将上下文对象作为接收者(this
)
选择it的场景:当上下文对象在作用域中主要用作函数调用中的参数时,使用 it
作为上下文对象会更好。若在代码块中使用多个变量,则 it
也更好。
总结
函数 | 类型 | 参数类型(it,this) | 返回值(本身,最后一行) |
---|---|---|---|
run | 非拓展函数 | it | 最后一行 |
with | 非拓展函数 | this | 最后一行 |
T.let | 拓展函数 | it | 最后一行 |
T.run | 拓展函数 | this | 最后一行 |
T.also | 拓展函数 | it | 本身 |
T,apply | 拓展函数 | this | 本身 |