Kotlin Scope Function

在 Kotlin 中,经常会发现使用 let 、apply 关键字后面带有代码块的写法。这些用法是 Kotlin 标准库提供的作用域函数。它们的典型特点是:

通过一个对象调用这些函数,这个对象称为**上下文对象 **。这些函数是 lambda 表达式的形式使用。在 lambda 表达式中,可以访问上下文对象

上下文对象.函数名 { 上下文对象引用 ->
    ...
}

当然也有比较特殊的 with 和 run 等。实际上有 6 个作用域函数:letrun 、 非拓展函数的 runwithapplyalso

这些函数的作用基本上是一样的:在一个对象上执行一段代码。不同的是这个对象如何在代码块中使用,以及代码块最终的返回结果。

例如,下面这个典型的用法:

Person("Alice"20"Amsterdam").let { it ->
    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)

作用域函数没有引入任何新的技术能力,但它们可以让代码更加简洁易读。

由于作用域函数的性质相似,在实际情况中选择正确的函数可能有点棘手。下面我们将详细描述作用域函数之间的区别及其使用约定。

不同之处

为了帮助您为您的目的选择正确的作用域函数,我们提供了它们之间的主要区别表。

函数 对象引用 返回值 是否是拓展函数
let it Lambda result YES
run this Lambda result YES
run Lambda result NO,在没有上下文对象的情况下调用
with this Lambda result NO,需要一个上下文对象作为参数
apply this Context 对象本身 YES
also it Context 对象本身 YES




为了方便区分,可以从以下角度进行记忆:

  • run 函数有两个。分别是拓展函数的情况,此时需要通过一个上下文对象调用;另一种情况是直接使用 run 方法,此时不需要依靠任何对象来调用。
  • 非拓展函数的作用域函数有两个,run 和 with ,其他都是拓展函数。它们都比较特殊,不需要依赖上下文对象进行调用。run 是无需任何对象就可以调用;with 需要将一个对象作为参数调用。
  • apply 和 also 的返回值是上下文对象本身,其他的都是 lambda 表达式的返回结果。
  • 只有 let 和 also 函数的 lambda 表达式中,通过 it 对上下文对象进行使用。
  • 非拓展函数 run 不需要依赖一个对象进行调用,所以它没有对象引用。

总体来说,除了拓展函数的区别,作用域函数最重要的区别就是 lambda 中引用上下文对象的方式不同和返回值不同。

上下文对象的引用方式

在作用域函数的 lambda 内部,上下文对象可通过一个代称来进行引用而不是实际变量名本身。每个作用域函数使用两种方式之一来访问上下文对象:

  • 作为 lambda 接收器 (this)
  • 作为 lambda 参数 (it)。

两者都提供相同的功能,因此我们将针对不同情况描述每种功能的优缺点,并就它们的使用提供建议。

fun main() {
    val str = "Hello"
    // this
    str.run {
        println("The string's length: $length")
    }

    // it
    str.let {
        println("The string's length is ${it.length}")
    }
}

this

runwithapply 将上下文对象称为 lambda 接收器,在它们的 lambda 中使用通过 this 关键字访问上下文对象。在类中使用自身,也是可以通过 this 关键字访问自身实例的。因此,该对象就像在普通类的函数中一样使用。

this 的好处是,可以直接省略 this 本身,直接访问对象,代码更短。但这样也会让你不好区分访问的是类本身中的内容,还是上下文对象中的内容。

因此,对于主要对上下文对象的属性或方法进行操作的 lambda,建议将上下文对象作为接收器 (this):主要是调用上下文对象自身的函数或为其分配属性。

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

it

反过来, let 也将上下文对象作为 lambda 参数。如果未指定参数名称,则通过隐式默认名称访问对象。it 比 this 更短,并且带有 it 的表达式通常比 this 表达式更容易理解。但是 it 不支持 this 一样的隐式调用,不可以省略。因此,当对象主要用作函数调用中的参数时,将上下文对象作为 it 是更合适的。

总结

  • it 更适用于将上下文对象作为参数,或代码块存在多个变量时使用。
  • this 更适用于对上下文对象本身的属性和方法进行操作时使用。

返回值

作用域函数因返回的结果而异,可以分为两组:

  • apply  和 also 返回上下文对象本身。
  • let 、run 和 with 返回 lambda 表达式的结果。

这两个选项可让您根据代码中的下一步操作来选择正确的函数。

上下文对象

apply 和 also 的返回值是上下文对象本身。因此,它们可以使用一个调用链来做一些附加操作。调用一些链式调用方法在同一个对象上附加额外功能。

val numberList = mutableListOf<Double>()
numberList.also { println("Populating the list") }
    .apply {
        add(2.71)
        add(3.14)
        add(1.0)
    }
    .also { println("Sorting the list") }
    .sort()

它们也可以用于返回上下文对象的函数的返回语句中。

fun getRandomInt()Int {
    return Random.nextInt(100).also {
        writeToLog("getRandomInt() generated value $it")
    }
}

val i = getRandomInt()

Lambda 结果

Let 、run 和 with 返回 lambda 表达式结果。因此你可以在需要将结果分配给变量时使用这些方法。

val numbers = mutableListOf("one""two""three")
val countEndsWithE = numbers.run { 
    add("four")
    add("five")
    count { it.endsWith("e") }
}
println("There are $countEndsWithE elements that end with e.")

此外,您可以忽略返回值并使用范围函数为变量创建临时范围。

val numbers = mutableListOf("one""two""three")
with(numbers) {
    val firstItem = first()
    val lastItem = last()        
    println("First item: $firstItem, last item: $lastItem")
}

选择困难

这些函数看起来功能并没有太多差别,如何在实际的代码逻辑中选择,官方给我们提供了一些规则:

  • 在非空对象上执行 lambda :let ,经常配合可空安全运算符 ? 使用。
  • 在局部范围内将表达式作为变量使用: let
  • 对象配置:apply
  • 对象配置并计算一个结果:run
  • 在需要一个表达式的地方运行语句:非拓展函数的 run
  • 附加一些效果:also
  • 对对象进行分组函数调用:with

尽管作用域函数是一种使代码更简洁的方法,但请避免过度使用它们:它会降低代码的可读性并导致错误。

避免嵌套作用域函数并在链接它们时要小心:很容易对当前上下文对象和 this 或 it 的值感到困惑。

let

上下文对象可用作参数 (it)。返回值是 lambda 结果。let 通常用于执行仅具有非空值的代码块。要对可空对象执行操作,需要配合 ? 进行使用。使用 let 的另一种情况是引入具有有限范围的局部变量以提高代码可读性。可以将参数 it 命名为其他名称,更容易理解。

with

with 不是拓展函数,上下文对象需要作为参数传递给 with 。with 方法推荐在不需要 lambda 返回结果时使用,意义是 “使用此对象,执行一些操作” 。

拓展函数 run

拓展函数的 run 做的事情和 with 相同。

非拓展函数的 run

非拓展函数的 run 不依赖上下文对象,只是执行一个 lambda 代码块。

apply

上下文对象可用作接收器 (this)。返回值是对象本身。 apply 的常见情况是对象配置。此类调用可以理解为“将以下分配应用于对象”。

also

上下文对象可用作参数 (it)。返回值是对象本身。适用于执行一些将上下文对象作为参数的操作。用于需要引用对象而不是其属性和函数的操作,或者当您不想从外部范围隐藏 this 引用时。


原文始发于微信公众号(八千里路山与海):Kotlin Scope Function

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/85044.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!