掌握Kotlin标准函数:run, with, let, also and apply

原文连接java

Kotlin的一些标准函数很是类似,咱们不肯定使用哪一个函数。在这里我将介绍一个简单的方法来清楚地区分他们的差别和如何选择使用。git

范围函数

我重点关注run, with, T.run, T.let, T.also and T.apply函数。我称他们为范围函数,由于我认为他们的主要功能是为调用函数提供一个内部范围。github

run函数是说明最简单的范围方法web

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并打印以前,它被彻底封闭在run范围内。编程

这个范围函数自己彷佛不是颇有用。可是相比范围,还有一点不错的是,它返回范围内最后一个对象。app

所以,下面代码将是很纯洁的,咱们能够像下面同样,将show()方法应用到两个 view,而不是 调用两次。函数

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

范围函数的3个属性

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

1.正常vs.扩展函数

若是咱们看看定义,with而且T.run这两个函数实际上很是类似。下面示例实现功能是同样的。this

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

然而,它们的不一样之处在于with是正常函数,而T.run是扩展函数。spa

那么问题是,每一个的优势是什么?

想象一下,若是webview.settings多是空的,那么看起来就像下面同样了。

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

webview.settings?.run {
    javaScriptEnabled = true
    databaseEnabled = true
}
复制代码

在这种状况下,显然T.run扩展功能比较好,由于在使用以前咱们能够判空。

2.This vs. it参数

若是咱们看看定义,,T.run而且T.let这两个函数除了接受参数的方式不同外几乎是同样的。如下两个函数的逻辑是相同的。

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

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

若是你检查T.run函数签名,你会注意到T.run只是做为扩展函数调用block: T.()。所以,全部的范围内,T能够被称为this。在编程中,this大部分时间能够省略。所以,在咱们上面的例子中,咱们能够在println声明中使用$length,而不是${this.length}。我把这称为传递this参数

然而,对于T.let函数签名,你会注意到T.let把本身做为参数传递进去,即block: (T)。所以,这就像传递一个lambda参数。它能够在做用域范围内使用it做为引用。因此我把这称为传递it参数

从上面看,它彷佛T.run是更优越,由于T.let更隐含,可是这是T.let函数有一些微妙的优点以下:

  • T.let相比外部类函数/成员,使用给定的变量函数/成员提供了更清晰的区分
  • this不能被省略的状况下,例如当它做为函数的参数被传递时itthis更短,更清晰。
  • T.let容许使用更好的变量命名,你能够转换it为其余名称。
stringVariable?.let {
      nonNullString ->
      println("The non null string is $nonNullString")
}
复制代码

3.返回当前类型 vs.其余类型

如今,咱们来看看T.letT.also,若是咱们看它们的内部函数范围,使用起来是同样的

stringVariable?.let {
      println("The length of this String is ${it.length}")
}
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"
// 改变值而且传递到下一链条
original.let {
    println("The original String is $it") // "abc"
    it.reversed() // 改变参数而且传递到下一链条
}.let {
    println("The reverse String is $it") // "cba"
    it.length   // 改变类型
}.let {
    println("The length of the String is $it") // 3
}
// 错误
// 在链中发送相同的值(打印的答案是错误的)
original.also {
    println("The original String is $it") // "abc"
    it.reversed() // 即便咱们改变它,也是没用的
}.also {
    println("The reverse String is ${it}") // "abc"
    it.length  // 即便咱们改变它,也是没用的
}.also {
    println("The length of the String is ${it}") // "abc"
}

// also经过修改原始字符串也能够达到一样目的
// 在链中发送相同的值
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. 在使用以前,它能够实现很是强大的自我操纵,实现链条建设者操做(builder 模式)。

当二者结合在一块儿时,即一个自我演变,一个自我保留,能够变得很是强大,例以下面

// 正常方法
fun makeDir(path: String): File  {
    val result = File(path)
    result.mkdirs()
    return result
}
// 改进方法
fun makeDir(path: String) = path.let{ File(it) }.also{ it.mkdirs() }
复制代码

全部的属性

经过说明这3个属性,咱们应该能够了解这些函数的行为了。让咱们再来看看T.apply函数,由于上面没有提到。这3个属性在T.apply定义以下...

  1. 这是一个扩展函数
  2. this做为参数传递。
  3. 它返回this(即它自己)

所以,能够想象,它能够像下面同样被使用

// 正常方法
fun createInstance(args: Bundle) : MyFragment {
    val fragment = MyFragment()
    fragment.arguments = args
    return fragment
}
// 改进方法
fun createInstance(args: Bundle) 
              = MyFragment().apply { arguments = args }
复制代码

或者咱们也能够建立链式调用。

// 正常方法
fun createIntent(intentData: String, intentAction: String): Intent {
    val intent = Intent()
    intent.action = intentAction
    intent.data=Uri.parse(intentData)
    return intent
}
// 改进实现
fun createIntent(intentData: String, intentAction: String) =
        Intent().apply { action = intentAction }
                .apply { data = Uri.parse(intentData) }
复制代码

函数选择

所以,显然,有了这三个属性,咱们如今能够对上述函数进行相应的分类。在此基础上,咱们能够在下面造成一个决策树,能够帮助咱们决定使用哪一个函数。

img


但愿上面的决策树能够清晰说明函数区别,也简化您的决策,使您可以恰当掌握这些函数的使用。

相关文章
相关标签/搜索