掌握Kotlin標(biāo)準(zhǔn)函數(shù):run, with, let, also and apply,這一篇就夠了

掌握Kotlin標(biāo)準(zhǔn)函數(shù):run, with, let, also and apply

Kotlin的一些標(biāo)準(zhǔn)函數(shù)非常相似,我們不確定使用哪個(gè)函數(shù)。在這里我將介紹一個(gè)簡(jiǎn)單的方法來清楚地區(qū)分他們的差異和如何選擇使用。

范圍函數(shù)

我重點(diǎn)關(guān)注 run, with, T.run, T.let, T.also and T.apply函數(shù)。我稱他們?yōu)榉秶瘮?shù),因?yàn)槲艺J(rèn)為他們的主要功能是為調(diào)用函數(shù)提供一個(gè)內(nèi)部范圍。

run函數(shù)是說明最簡(jiǎn)單的范圍方法

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

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

有了這個(gè)函數(shù),在 test函數(shù)內(nèi)部,你可以有一個(gè)單獨(dú)的范圍, mood在重新定義為 I am happy并打印之前,它被完全封閉在 run范圍內(nèi)。

這個(gè)范圍函數(shù)本身似乎不是很有用。但是相比范圍,還有一點(diǎn)不錯(cuò)的是,它返回范圍內(nèi)最后一個(gè)對(duì)象。

因此,下面代碼將是很純潔的,我們可以像下面一樣,將 show()方法應(yīng)用到兩個(gè) view,而不是 調(diào)用兩次。

run {
      if (firstTimeView) introView else normalView
    }.show()

范圍函數(shù)的3個(gè)屬性

為了使范圍函數(shù)更有趣,讓我用3個(gè)屬性將他們的行為分類,并且使用這些屬性來區(qū)分它們。

1.正常vs.擴(kuò)展函數(shù)

如果我們看看定義,with并且T.run這兩個(gè)函數(shù)實(shí)際上非常相似。下面示例實(shí)現(xiàn)功能是一樣的。

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


然而,它們的不同之處在于 with是正常函數(shù),而 T.run是擴(kuò)展函數(shù)。

那么問題是,每個(gè)的優(yōu)點(diǎn)是什么?

想象一下,如果 webview.settings可能是空的,那么看起來就像下面一樣了。

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

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

在這種情況下,顯然 T.run擴(kuò)展功能比較好,因?yàn)樵谑褂弥拔覀兛梢耘锌铡?/p>

2.This vs. it參數(shù)

如果我們看看定義,, T.run并且 T.let這兩個(gè)函數(shù)除了接受參數(shù)的方式不一樣外幾乎是一樣的。以下兩個(gè)函數(shù)的邏輯是相同的。

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

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

如果你檢查 T.run函數(shù)簽名,你會(huì)注意到 T.run只是作為擴(kuò)展函數(shù)調(diào)用 block: T.()。因此,所有的范圍內(nèi), T可以被稱為 this。在編程中, this大部分時(shí)間可以省略。因此,在我們上面的例子中,我們可以在 println聲明中使用 $length,而不是 ${this.length}。我把這稱為傳遞 this參數(shù)。

然而,對(duì)于 T.let函數(shù)簽名,你會(huì)注意到 T.let把自己作為參數(shù)傳遞進(jìn)去,即 block: (T)。因此,這就像傳遞一個(gè)lambda參數(shù)。它可以在作用域范圍內(nèi)使用 it作為引用。所以我把這稱為傳遞 it參數(shù)。

從上面看,它似乎 T.run是更優(yōu)越,因?yàn)?T.let更隱含,但是這是 T.let函數(shù)有一些微妙的優(yōu)勢(shì)如下:

  • T.let相比外部類函數(shù)/成員,使用給定的變量函數(shù)/成員提供了更清晰的區(qū)分
  • this不能被省略的情況下,例如當(dāng)它作為函數(shù)的參數(shù)被傳遞時(shí) itthis更短,更清晰。
  • T.let允許使用更好的變量命名,你可以轉(zhuǎn)換 it為其他名稱。
stringVariable?.let {
      nonNullString ->
      println("The non null string is $nonNullString")
}

3.返回當(dāng)前類型 vs.其他類型

現(xiàn)在,我們來看看T.letT.also,如果我們看它們的內(nèi)部函數(shù)范圍,使用起來是一樣的

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。

兩者對(duì)于鏈接函數(shù)都是有用的,通過 T.let你可以演變操作,通過 T.also你在同一個(gè)變量 this上執(zhí)行操作。

簡(jiǎn)單的例子如下

val original = "abc"
// 改變值并且傳遞到下一鏈條
original.let {
    println("The original String is $it") // "abc"
    it.reversed() // 改變參數(shù)并且傳遞到下一鏈條
}.let {
    println("The reverse String is $it") // "cba"
    it.length   // 改變類型
}.let {
    println("The length of the String is $it") // 3
}
// 錯(cuò)誤
// 在鏈中發(fā)送相同的值(打印的答案是錯(cuò)誤的)
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通過修改原始字符串也可以達(dá)到同樣目的
// 在鏈中發(fā)送相同的值
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好像毫無意義,因?yàn)槲覀兛梢院苋菀椎貙⑺鼈兘M合成一個(gè)功能塊。但仔細(xì)想想,它也有一些優(yōu)點(diǎn):

  1. 它可以在相同的對(duì)象上提供一個(gè)非常清晰的分離過程,即制作更小的功能部分。
  2. 在使用之前,它可以實(shí)現(xiàn)非常強(qiáng)大的自我操縱,實(shí)現(xiàn)鏈條建設(shè)者操作(builder 模式)。

當(dāng)兩者結(jié)合在一起時(shí),即一個(gè)自我演變,一個(gè)自我保留,可以變得非常強(qiáng)大,例如下面

// 正常方法
fun makeDir(path: String): File  {
    val result = File(path)
    result.mkdirs()
    return result
}
// 改進(jìn)方法
fun makeDir(path: String) = path.let{ File(it) }.also{ it.mkdirs() }


所有的屬性

通過說明這3個(gè)屬性,我們應(yīng)該可以了解這些函數(shù)的行為了。讓我們?cè)賮砜纯?T.apply函數(shù),因?yàn)樯厦鏇]有提到。這3個(gè)屬性在 T.apply定義如下...

  1. 這是一個(gè)擴(kuò)展函數(shù)
  2. this作為參數(shù)傳遞。
  3. 它返回this(即它本身)

因此,可以想象,它可以像下面一樣被使用

// 正常方法
fun createInstance(args: Bundle) : MyFragment {
    val fragment = MyFragment()
    fragment.arguments = args
    return fragment
}
// 改進(jìn)方法
fun createInstance(args: Bundle) 
              = MyFragment().apply { arguments = args }


或者我們也可以創(chuàng)建鏈?zhǔn)秸{(diào)用。

// 正常方法
fun createIntent(intentData: String, intentAction: String): Intent {
    val intent = Intent()
    intent.action = intentAction
    intent.data=Uri.parse(intentData)
    return intent
}
//  改進(jìn)實(shí)現(xiàn)
fun createIntent(intentData: String, intentAction: String) =
        Intent().apply { action = intentAction }
                .apply { data = Uri.parse(intentData) }


函數(shù)選擇

因此,顯然,有了這三個(gè)屬性,我們現(xiàn)在可以對(duì)上述函數(shù)進(jìn)行相應(yīng)的分類。在此基礎(chǔ)上,我們可以在下面形成一個(gè)決策樹,可以幫助我們決定使用哪個(gè)函數(shù)。

image

希望上面的決策樹可以清晰說明函數(shù)區(qū)別,也簡(jiǎn)化您的決策,使您能夠恰當(dāng)掌握這些函數(shù)的使用。

(每天學(xué)習(xí)一點(diǎn)點(diǎn).每天進(jìn)步一點(diǎn)點(diǎn),分享不宜路過點(diǎn)個(gè)贊呀,喜歡的點(diǎn)個(gè)關(guān)注后續(xù)更新不斷)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容