[译]学会 Kotlin 标准函数: run, with, let, also and apply

翻译说明:

原标题: Mastering Kotlin standard functions: run, with, let, also and apply

原文地址: medium.com/@elye.proje…

原文作者: Elye

Kotlin 有一些相似的标准函数,我们不确定该使用哪种函数。接下来,我将介绍一种简单的方法来清楚地区分它们的差异以及如何选择使用。

作用域函数

我会专注于函数的 run,with,T.run,T.let,T.also 和 T.apply。我称他们为作用域函数,我将它们的主要功能视为:为其调用者函数提供的内部作用域。

说明范围中最简单方法是运行函数

fun test() {
    var mood = "I am sad"

    run {
        val mood = "I am happy"
        println(mood) // I am happy
    }
    println(mood)  // I am sad
}
复制代码

有了这个,在测试函数内部,你可以有一个单独的范围,在打印之前 mood 重新定义 I am happy,并且它完全包含在运行范围内。

这个作用域函数本身似乎不是很有用。但它有另一个好处,而不仅仅是范围;它返回一些东西,即范围内的最后一个对象。

因此,下面将是整洁的,我们可以将 show() 两个视图应用于下面,而不是两次调用它。

 run {
        if (firstTimeView) introView else normalView
    }.show()
复制代码

作用域中的3个属性

为了使范围函数更有趣,让我用3个属性对它们的行为进行分类。我将使用这些属性来区分它们。

1.标准与扩展函数

如果我们看一下 with 和 T.run,这两个函数实际上是相当类似的。以下是同样的事情。


with(webview.settings) {
    javaScriptEnabled = true
    databaseEnabled = true
}

// similarly
webview.settings.run {
    javaScriptEnabled = true
    databaseEnabled = true
}

复制代码

然而,它们的不同之处在于一个是标准函数,即 with 另一个是扩展函数 T.run。

所以问题是,每个的优势是什么?

想象一下,如果 webview.settings 可能为 null,它们将如下所示。


// Yack!
with(webview.settings) {
      this?.javaScriptEnabled = true
      this?.databaseEnabled = true
}

// Nice.
webview.settings?.run {
    javaScriptEnabled = true
    databaseEnabled = true
}

复制代码

在这种情况下,明确的 T.run 扩展函数更好,因为我们可以在使用之前应用检查可空性。

2. This vs. it 实际参数

If we look at T.run and T.let, both functions are similar except for one thing, the way they accept the argument. The below shows the same logic for both functions.

如果我们看一下 T.run 和 T.let,除了一件事俩个函数都是相似的,他们接受实际参数的方式类似。以下显示了俩种逻辑相同的函数。


stringVariable?.run {
      println("The length of this String is $length")
}

// Similarly.

stringVariable?.let {
      println("The length of this String is ${it.length}")
}

复制代码

如果你检查 T.run 函数签名,你会发现 T.run 它只是作为扩展函数调用 block: T.()。因此,在范围内,T 可以称为 this。在编程中,this 大多数时候可以省略。因此,在上面的示例中,我们可以 {length} 在 println 语句中使用,而不是 {this.length}。我称之为发送此作为参数。

但是对于 T.let 函数签名,您会注意到它 T.let 正在将自身发送到函数中,即 block: (T)。因此,这就像发送它的 lambda 实际参数。它可以在范围函数中引用为 it。所以我称之为发送它作为实际参数。

从上面看,它似乎 T.run 更优越,T.let 因为它更隐含,但功能有一些微妙的优点 T.let 如下: -

  • 所述 T.let 确实提供更清晰的区分使用给定的变量函数/构件与外部类功能/部件

  • 在 this 不能省略的情况下,例如当它作为函数的参数发送时 it,写入比写入 this 更短并且更清晰。

  • 在 T.let 允许转换使用变量的命名更好,你即可以转换 it 为其他名称。


stringVariable?.let {
      nonNullString ->
      println("The non null string is $nonNullString")
}

复制代码

3. 返回 this vs. 其他类型

现在,让我们来看看 T.let 和 T.also,两者是相同的,如果我们看看它的内部函数范围。


stringVariable?.let {
      println("The length of this String is ${it.length}")
}

// Exactly the same as below

stringVariable?.also {
      println("The length of this String is ${it.length}")
}

复制代码

然而,他们微妙的不同就是他们的返回。所述 T.let 返回不同类型的值,而 T.also 返回 T 本身即 this。

两者都对链接函数很有用,其中 T.let 让你进化操作,然后 T.also 让你对同一个变量执行即 this。

简单说明如下


val original = "abc"

// Evolve the value and send to the next chain
original.let {
    println("The original String is $it") // "abc"
    it.reversed() // evolve it as parameter to send to next let
}.let {
    println("The reverse String is $it") // "cba"
    it.length  // can be evolve to other type
}.let {
    println("The length of the String is $it") // 3
}

// Wrong
// Same value is sent in the chain (printed answer is wrong)
original.also {
    println("The original String is $it") // "abc"
    it.reversed() // even if we evolve it, it is useless
}.also {
    println("The reverse String is ${it}") // "abc"
    it.length  // even if we evolve it, it is useless
}.also {
    println("The length of the String is ${it}") // "abc"
}

// Corrected for also (i.e. manipulate as original string
// Same value is sent in the chain 
original.also {
    println("The original String is $it") // "abc"
}.also {
    println("The reverse String is ${it.reversed()}") // "cba"
}.also {
    println("The length of the String is ${it.length}") // 3
}

复制代码

在 T.also 似乎毫无意义上面,因为我们可以很容易地将它们组合成一个单独的函数。仔细思考,它有一些很好的优势。

  1. 它可以在相同的物体上提供非常清晰的分离过程,即制作较小的函数部分。

  2. 在使用之前,它可以非常强大的自我操作,使链接构建器操作。

当俩者结合起来是,一个继续演化,一个保持不变,它变得更强,例如下面


// Normal approach
fun makeDir(path: String): File  {
    val result = File(path)
    result.mkdirs()
    return result
}

// Improved approach
fun makeDir(path: String) = path.let{ File(it) }.also{ it.mkdirs() }

复制代码

看所有的属性

通过查看3个属性,我们几乎可以知道函数行为。让我说明一下这个 T.apply 函数,因为它没有在上面提到过。3个属性 T.apply 如下......

  1. 这是一个扩展函数
  2. 它发送的 this 是一个实际参数
  3. 它返回 this (i.e. itself)

因此,使用它,可以想象,它可以用作


// Normal approach
fun createInstance(args: Bundle) : MyFragment {
    val fragment = MyFragment()
    fragment.arguments = args
    return fragment
}

// Improved approach
fun createInstance(args: Bundle) 
              = MyFragment().apply { arguments = args }
              
复制代码

或者我们也可以创建链式对象。

// Normal approach
fun createIntent(intentData: String, intentAction: String): Intent {
    val intent = Intent()
    intent.action = intentAction
    intent.data=Uri.parse(intentData)
    return intent
}

// Improved approach, chaining
fun createIntent(intentData: String, intentAction: String) =
        Intent().apply { action = intentAction }
                .apply { data = Uri.parse(intentData) }

复制代码

函数选择

因此很明显,有了3个属性,我们现在可以相应地对函数进行分类。基于此,我们可以在下面形成一个决策树,可以帮助决定我们想要使用的功能等待我们需要的功能。

希望上面的决策树能够更清楚地说明函数,并简化您的决策,使您能够恰当地掌握这些函数的使用。

随意提供一些很好的实例,说明如何使用这些函数作为对此博客的回应。我很乐意听到你的消息。这可能有益于其他人。

我希望你感谢这篇文章,它对你有所帮助。与他人分享。

你可以在这里查看我的其他有趣话题。

MediumTwitterFacebook上关注我,获取有关Android,Kotlin等相关主题的小技巧和知识。〜Elye〜


欢迎关注 Kotlin 中文社区!

中文官网:www.kotlincn.net/

中文官方博客:www.kotliner.cn/

公众号:Kotlin

知乎专栏:Kotlin

CSDN:Kotlin中文社区

掘金:Kotlin中文社区

简书:Kotlin中文社区

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值