掌握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


但願上面的決策樹能夠清晰說明函數區別,也簡化您的決策,使您可以恰當掌握這些函數的使用。

相關文章
相關標籤/搜索