[译]掌握Kotlin中的标准库函数: run、with、let、also和apply

翻译说明:java

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

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

原文做者: Elyeless

Kotlin中的一些标准库函数很是类似,以至于咱们不肯定要使用哪一个函数。这里我将介绍一种简单的方法来清楚地区分它们之间的差别以及如何选择使用哪一个函数。函数

做用域函数

下面我将关于 run、with、T.run、T.let、T.also 和 T.apply 这些函数,并把它们称为做用域函数,由于我注意到它们的主要功能是为调用者函数提供内部做用域。学习

说明做用域最简单的方式是 run 函数this

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

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

在这个例子中,在test函数的内部有一个分隔开的做用域,在这个做用域内部彻底包含一个 在输出以前的mood 变量被从新定义并初始化为 I am happy的操做实现。编码

这个做用域函数自己彷佛看起来不是颇有用。可是这还有一个比做用域有趣一点是,它返回一些东西,是这个做用域内部的最后一个对象。spa

所以,如下的内容会变得更加整洁,咱们能够将show()方法应用到两个View中,而不须要去调用两次show()方法。翻译

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

做用域函数的三个属性特征

为了让做用域函数更有趣,让我把他们的行为分类成三个属性特征。我将会使用这些属性特征来区分他们每个函数。

一、普通函数 VS 扩展函数 (Normal vs. extension function)

若是咱们对比 withT.run 这两个函数的话,他们其实是十分类似的。下面使用他们实现相同的功能的例子.

with(webview.settings) {
    javaScriptEnabled = true
    databaseEnabled = true
}
// similarly
webview.settings.run {
    javaScriptEnabled = true
    databaseEnabled = true
}
复制代码

而后他们之间惟一不一样在于 with 是一个普通函数,而 T.run是一个扩展函数。

那么问题来了,它们各自使用的优势是什么?

想象一下若是 webview.settings 可能为空,那么下面两种方式实现如何去修改呢?

// Yack!(比较丑陋的实现方式)
with(webview.settings) {
      this?.javaScriptEnabled = true
      this?.databaseEnabled = true
   }
}
// Nice.(比较好的实现方式)
webview.settings?.run {
    javaScriptEnabled = true
    databaseEnabled = true
}
复制代码

在这种状况下,显然 T.run 扩展函数更好,由于咱们能够在使用它以前对可空性进行检查。

二、this VS it 参数(This vs. it argument)

若是咱们对比 T.runT.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是能够被省略的。所以咱们上面的示例中,咱们能够在println语句中直接使用 $length 而不是 ${this.lenght}. 因此我把这个称之为传递 this参数

然而对于 T.let 函数的声明,你将会注意到 T.let 是传递它本身自己到函数中block: (T)。所以这个相似于传递一个lambda表达式做为参数。它能够在函数做用域内部使用it来指代. 因此我把这个称之为传递 it参数

从上面看,彷佛T.runT.let更加优越,由于它更隐含,可是T.let函数具备一些微妙的优点,以下所示:

  • 一、T.let函数提供了一种更清晰的区分方式去使用给定的变量函数/成员与外部类函数/成员。
  • 二、例如当it做为函数的参数传递时,this不能被省略,而且it写起来比this更简洁,更清晰。
  • 三、T.let容许更好地命名已转换的已使用变量,便可以将it转换为其余有含义名称,而 T.run则不能,内部只能用this指代或者省略。
stringVariable?.let {
      nonNullString ->
      println("The non null string is $nonNullString")
}
复制代码

三、返回this VS 其余类型 (Return this vs. other type)

如今,让咱们看看T.letT.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类型自己,即这个。

这两个函数对于函数的链式调用都颇有用,其中T.let让您演变操做,而T.also则让您对相同的变量执行操做。

简单的例子以下:

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彷佛看上去没有意义,由于咱们能够很容易地将它们组合成一个功能块。仔细思考,它有一些很好的优势。

  • 一、它能够对相同的对象提供很是清晰的分离过程,即建立更小的函数部分。
  • 二、在使用以前,它能够很是强大的进行自我操做,从而实现整个链式代码的构建操做。

当二者结合在一块儿使用时,即一个自身演变,一个自我保留,它能使一些操做变得更增强大。

// 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函数,因为我并无以上函数中提到过它。 T.apply的三个属性以下

  • 一、它是一个扩展函数
  • 二、它是传递this做为参数
  • 三、它是返回 this (即它本身自己)

所以,使用它,能够想象下它能够被用做:

// 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个属性特征,咱们如今能够对功能进行相应的分类。基于此,咱们能够在下面构建一个决策树,以帮助肯定咱们想要使用哪一个函数,来选择咱们须要的。

但愿上面的决策树可以更清晰地说明功能,并简化你的决策,使你可以适当掌握这些功能的使用.

译者有话说

  • 一、为何我要翻译这篇博客?

咱们都知道在Kotlin中Standard.Kt文件中短短不到100来行库函数源码,可是它们做用是很是强大,能够说它们是贯穿于整个Kotlin开发编码过程当中。使用它们能让你的代码会更具备可读性、更优雅、更简洁。善于合理使用标准库函数,也是衡量你对Kotlin掌握程度标准之一,由于你去看一些开源Kotlin源码,随处可见的都是使用各类标准库函数。

可是这些库函数有难点在于它们的用法都很是类似,有的人甚至认为有的库函数都是多余的,其实否则,每一个库函数都是有它的实际应用场景。虽然有时候你能用一种库函数也能实现相同的功能,可是也许那并非最好的实现方式。相信不少初学者对于这些标准库函数也是傻傻分不清楚(曾经的我也是),可是这篇博客很是一点在于它提取出了这些库函数三个主要特征:是不是扩展函数、是否传递this或it作为参数(在函数内部表现就是this和it的指代)、是否须要返回调用者对象自己,基于特征就能够进行分类,分类后相应的应用场景也就一目了然。这种善于提取特征思路仍是值得学习的。

  • 二、关于使用标准库函数须要补充的几点。

第一点: 建议尽可能不要使用多个标准库函数进行嵌套,不要为了简化而去作简化,不然整个代码可读性会大大下降,一会是it指代,一会又是this指代,估计隔一段时间后连你本身都不知道指代什么了。

第二点: 针对上面译文的let函数和run函数须要补充下,他们之因此可以返回其余类型的值,其原理在于内部block lambda表达式返回的R类型,也就是这二者函数的返回值类型取决于传入block lambda表达式返回类型,然而决定block lambda表达式返回值类型,取决于外部传入lambda表达式体内最后一行返回值

第三点: 关于T.also和T.apply函数为何都能返回本身自己,是由于在各自Lambda表达式内部最后一行都调用return this,返回它们本身自己,这个this能被指代调用者,是由于它们都是扩展函数特性

  • 三、总结

关于标准库函数本篇译文在于告知应用场景以及理清它们的区别以及在使用库函数简化代码实现时要掌握好度,不要滥用不然你的代码可读性会不好,后续会深刻每一个标准库函数内部原理作分析,欢迎关注。

欢迎关注Kotlin开发者联盟,这里有最新Kotlin技术文章,每周会不按期翻译一篇Kotlin国外技术文章。若是你也喜欢Kotlin,欢迎加入咱们~~~

相关文章
相关标签/搜索