Kotlin 的标准函数 run,let,also,apply

Kotlin 的标准函数 run,let,also,apply_第1张图片

Kotlin 里面的run,let,also,apply 就像图片中的木头铲子一样非常相似,有些时候我们不太清楚实战中应该使用哪一个标准函数,下面我们举一些例子来说明它们的区别。

开始

咱们先看 run{} 标准函数最简单的一个用法:

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

    run {
        val mood = "I am happy"
        println(mood) // I am happy
    }
    println(mood)  //I am sad
}

在 world 这个函数里面,你可以有一个单独的范围,重新定义mood。

来一个比较抽象的解释:你在这个世界每天要面对烦心的事情,心情比较沮丧(sad),但是你只要回到家里没有外界的干扰,心情就很美丽(happy),run {} 就好比你的家一样,是隔离外界的一个范围。

但是这个范围貌似没有太大的卵用,除了这个范围,还有没有其他的好处嘞?

//通常写法
if (first) {
    view1.show()
} else {
    view2.show()
}

//run 写法
run {
    if(first) view1 else view2
} .show()

run {} 函数的写法不需要我们调用两次show()方法,代码量也有减少

标准函数的3个属性

我用下面这3个属性来区分这些标准函数它们之间的差别

1. 普通写法 和 扩展函数

webview.settings.javaScriptEnabled = true
webview.settings.databaseEnabled = true

// 效果一样

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

看上面的代码,一个是普通的写法,一个是用 run{} 的写法,那run{}的好处是什么呢?

咱们假设 webview.settings 有可能为 null,那我们的写法就是这样的:

webview.settings?.javaScriptEnabled = true
webview.settings?.databaseEnabled = true

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

 run{} 的写法可以让我们在调用方法之前就判断 webview.settings 是否为 null,而普通的写法则需要两次判空

2. 参数 this 和 还是 it

下面的代码 run{} 和 let{} 他们范围里面的参数是不一样的,一个是this,一个是it

stringVariable?.run {
      println("The length of this String is $length")
}
// 打印相同的内容
stringVariable?.let {
      println("The length of this String is ${it.length}")
}

从上面看来好像 run{} 更好一点,因为代码量上少了那么一丢丢,但其实 let{} 也有自己的优点:

 it 只是他的参数的默认名字,他还可以自定义参数的名称。

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

3. 返回值 return this 还是 return other

下面我们来看一下 let{} 和 also{} ,如果单纯从函数的范围和参数来看,他们的效果是一样的

stringVariable?.let {
      println("The length of this String is ${it.length}")
}
//效果是一样的
stringVariable?.also {
      println("The length of this String is ${it.length}")
}

let{} 和 also{} 的区别在于他们的返回值是不一样的,let{} 的返回值是大括号里面的最后一行内容,also{} 返回的是它自己也就是this

val original = "abc"

original.let {
    println("最开始的字符串是 $it") // "abc"
    it.reversed() // it.reversed()会作为参数传给下一个 let
}.let {
    println("反转后的字符串是 $it") // "cba"
    it.length  // it.length 会作为参数传给下一个 let
}.let {
    println("字符串的长度是 $it") // 3
}

//同样使用 also 会打印不一样的结果
original.also {
    println("最开始的字符串是 $it") // "abc"
    it.reversed()// 虽然调用的反转,但传给下一个 also 的是 original 而不是 it.reversed()。
}.also {
    println("所以这里输出的还是 $it") // "abc"
    it.length // 这里调用了 length 但传给下一个 also 的还是 original 而不是 it.length。
}.also {
    println("所以这里输出的还是 $it") // "abc"
}

//那如果你非要使用 also 达到同样的效果的话,那应该这样写
original.also {
    println("最开始的字符串是 $it") // "abc"
}.also {
    println("反转后的字符串是 ${it.reversed()}") // "cba"
}.also {
    println("字符串的长度是 ${it.length}") // 3
}

还有最后一个 apply{} 对应上面的3个属性就是:

  1. apply{} 也是个扩展函数
  2. 范围内的参数是 this
  3. 返回值 return this
// 普通写法
fun createIntent(username: String, password: String): Intent {
    val intent = Intent()
    intent.putString("username", username)
    intent.putString("password", password)
    return intent
}

// apply 的写法
fun createIntent(username: String, password: String) =
        Intent().apply { putExtra("username", username) }
            .apply { putExtra("password", password) }

实战

 在实战中使用标准函数,很多时候都能达到惊人的效果,不仅代码量少,链式代码使可读性大大提高。

// 普通写法
fun makeDir(path: String): File  {
    val result = File(path)
    result.mkdirs()
    return result
}
// 使用标准函数的写法
fun makeDir(path: String) = path.let{ File(it) }.also{ it.mkdirs() }

总结 

 基于上面的三个属性,我们总结一下,怎样在实战中选择正确的那一个函数?

下面是一段伪代码,希望它能帮到你。

if (is需要返回自己?){
    //是 return this
    when (范围内的参数){
        it -> {
            使用 also {}
        }
        this -> {
            使用 apply {}
        }
    }
} else {
    //否,return other
    when (范围内的参数){
        it -> {
            使用 let {}
        }
        this -> {
            使用 run {}
        }
    }
}

 

你可能感兴趣的:(Kotlin)