Kotlin 作用域函数

官方参考资料:https://kotlinlang.org/docs/reference/scope-functions.html

Kotlin 的 Scope Functions 包括 let、run、with、apply、also 等

Basically, these functions do the same: execute a block of code on an object. What’s different is how this object becomes available inside the block and what is the result of the whole expression.

Kotlin 中的作用域函数使用上非常相似,它们之间的主要区别在于:

  • 引用上下文对象的方式
    • run、with、apply 将 Context Object 作为 lambda 的接收器
    • let 和 also 则将上下文对象作为 lambda 表达式参数
  • 返回值
    • apply、also 返回上下文对象本身
    • let、run、with 返回 lambda 表达式的结果 (最后一条语句的执行结果)

举一个适合比较的例子,在 apply 和 let 的函数定义上即可看出:

public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

apply 函数需要一个 block 作为参数,这个参数的类型为 T.() -> Unit(在 Kotlin 中函数也能作为参数),这个类型表示 block 为一个无参且无返回值的函数,那么为什么不定义为 () -> Unit ,上面说到,apply 将上下文对象作为接收者,实际上它是 block 的接收者,通过这样的定义,我可以在 block 中通过 this 关键字使用该接收者的方法与属性,或是缺省方式直接引用方法与属性。而 apply 函数的返回类型为 T,即这个上下文对象。

与之相比,let 函数的定义更好理解了,let 函数同样需要一个 block 作为参数,这个参数是一个 以 T 为参数,以 R 为返回类型 的函数。因此根据 Kotlin 的语法特性,T 作为参数,我可以声明符合语法规定的参数名来使用它(默认为 it),而 block 函数的结果 R,实际上也是 let 函数的返回结果。

Fun1: apply()

apply 返回对象本身,适合在创建实例时,对非构造器参数做初始化

data class Person(var name: String, var age: Int = 0, var city: String = "")

fun main() {
    val adam = Person("Adam").apply { 
        age = 20                       // same as this.age = 20 or adam.age = 20
        city = "London"
    }
}

此处缺省 this 关键字,如果在该对象声明处所在Scope中有与之成员变量重名的
属性时候,需要添加this,即 this.length = 4.

Fun2: with()

with 不是一个扩展函数,而是直接将对象传入作为参数

We recommend with for calling functions on the context object without providing the lambda result. In the code, with can be read as “with this object, do the following.

官方推荐用 with 来调用上下文对象上的函数,而不使用 lambda 表达式的结果,这可被理解为“对于该对象,执行以下操作”。例如:

val numbers = mutableListOf("one", "two", "three")
with(numbers) {
    println("'with' is called with argument $this")
    println("It contains $size elements")
}

从语义上来说,以这种方式使用 with 函数是相对恰当的,但是也有另一种适合的使用场景:

val numbers = mutableListOf("one", "two", "three")
val firstAndLast = with(numbers) {
    "The first element is ${first()}," +
    " the last element is ${last()}"
}
println(firstAndLast)

with 引入一个辅助对象,通过该对象的方法和属性计算得到另一个结果并通过 lambda 表达式返回。为了方便从语义上理解,我认为这可以视作是“with(某个对象)得到结果”。

**适用场景:**需要大量使用某对象的方法时,例如,在Android开发中,通过 model 对 RecyclerView 的 item 进行数据绑定的时候。

Function3:let()

let 函数常用方式

Person("Alice", 20, "Amsterdam").let {
    println(it)
    it.moveTo("London")
    it.incrementAge()
    println(it)
}

以上不添加 .let 则是代码是这样的

val alice = Person("Alice", 20, "Amsterdam")
println(alice)
alice.moveTo("London")
alice.incrementAge()
println(alice)

区别在于后者必须引入变量来对 Person 对象做引用,而不巧的也许是变量 alice 在当前域中只使用这么一次,因此这种写法就不太优雅了

**适用场景:**仅执行某个类(或是 Util 类)中的方法,而这个类的对象并不会在下文产生引用

函数 对象引用 返回值 是否是扩展函数
let it Lambda 表达式结果
run this Lambda 表达式结果
run - Lambda 表达式结果 不是:调用无需上下文对象
with this Lambda 表达式结果 不是:把上下文对象当做参数
apply this 上下文对象
also it 上下文对象
发布了27 篇原创文章 · 获赞 0 · 访问量 93

猜你喜欢

转载自blog.csdn.net/charjindev/article/details/104312081
今日推荐