1. 程式人生 > >Kotlin標準函式run with let also apply的區別

Kotlin標準函式run with let also apply的區別

Kotlin的Standard.kt中的一些函式很相像,很多時候我們不確定使用哪個。這篇文章我將教大家如何區分他們的不同,以及如何在正確的場景下使用正確的函式

image

Standard.kt中有 run,T.run,with,T.apply,T.also,T.let幾個函式,他們的共同的功能都是執行程式。但是他們之間還是有區別的。

一共有6個函式。我將根據返回值以及block函式的引數對它歸類
image

  • T.apply和T.also是一對,它們的返回值為this,他們的區別在於apply block引數中傳遞的是this,also block引數中傳遞的是it
  • T.run和T.let是一對,它們的返回值是block執行的結果,它們的區別在於run block引數傳遞的是this,let block引數傳遞的是it
  • run和with是一對,run block引數中既不是this也不是it。with block引數是this

函式塊

首先我們用run函式來演示下函式塊

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

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

在函式塊中我們可以重新宣告mood變數

with和T.run對比

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

他們兩實現的功能是一樣的。如果webview.setting可能為null

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

this vs it引數

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大多數時候可以省略。因此,在上面的示例中,我們可以 l e n g t h p r i n t l n 使 length在println語句中使用,而不是 {this.length}。我稱之為傳送此作為引數。

返回this vs 返回other

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返回的是函式快的返回值Unit,而T.also返回T本身即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
}

使用

// 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() }
// 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) }