[譯]掌握Kotlin中的標(biāo)準(zhǔn)庫函數(shù): run跃须、with站叼、let、also和apply

(轉(zhuǎn)載)原文鏈接:https://juejin.im/post/5b0048ed518825428a2619ed


翻譯說明:

原標(biāo)題: Mastering Kotlin standard functions: run, with, let, also and apply

原文地址: medium.com/@elye.proje…

原文作者: Elye

Kotlin中的一些標(biāo)準(zhǔn)庫函數(shù)非常相似,以致于我們不確定要使用哪個函數(shù)菇民。這里我將介紹一種簡單的方法來清楚地區(qū)分它們之間的差異以及如何選擇使用哪個函數(shù)尽楔。

作用域函數(shù)

下面我將關(guān)于 run、with第练、T.run阔馋、T.let、T.also 和 T.apply 這些函數(shù)娇掏,并把它們稱為作用域函數(shù)呕寝,因?yàn)槲易⒁獾剿鼈兊闹饕δ苁菫檎{(diào)用者函數(shù)提供內(nèi)部作用域。

說明作用域最簡單的方式是 run 函數(shù)

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

    run {
        val mood = "I am happy"
        println(mood) // I am happy
    }
    println(mood)  // I am sad
}
復(fù)制代碼

在這個例子中婴梧,在test函數(shù)的內(nèi)部有一個分隔開的作用域下梢,在這個作用域內(nèi)部完全包含一個 在輸出之前的mood 變量被重新定義并初始化為 I am happy的操作實(shí)現(xiàn)。

這個作用域函數(shù)本身似乎看起來不是很有用志秃。但是這還有一個比作用域有趣一點(diǎn)是怔球,它返回一些東西,是這個作用域內(nèi)部的最后一個對象浮还。

因此竟坛,以下的內(nèi)容會變得更加整潔,我們可以將show()方法應(yīng)用到兩個View中,而不需要去調(diào)用兩次show()方法担汤。

run {
        if (firstTimeView) introView else normalView
}.show()
復(fù)制代碼

作用域函數(shù)的三個屬性特征

為了讓作用域函數(shù)更有趣涎跨,讓我把他們的行為分類成三個屬性特征。我將會使用這些屬性特征來區(qū)分他們每一個函數(shù)崭歧。

1隅很、普通函數(shù) VS 擴(kuò)展函數(shù) (Normal vs. extension function)

如果我們對比 withT.run 這兩個函數(shù)的話,他們實(shí)際上是十分相似的率碾。下面使用他們實(shí)現(xiàn)相同的功能的例子.

with(webview.settings) {
    javaScriptEnabled = true
    databaseEnabled = true
}
// similarly
webview.settings.run {
    javaScriptEnabled = true
    databaseEnabled = true
}
復(fù)制代碼

然后他們之間唯一不同在于 with 是一個普通函數(shù)叔营,而 T.run是一個擴(kuò)展函數(shù)。

那么問題來了所宰,它們各自使用的優(yōu)點(diǎn)是什么绒尊?

想象一下如果 webview.settings 可能為空,那么下面兩種方式實(shí)現(xiàn)如何去修改呢仔粥?

// Yack!(比較丑陋的實(shí)現(xiàn)方式)
with(webview.settings) {
      this?.javaScriptEnabled = true
      this?.databaseEnabled = true
   }
}
// Nice.(比較好的實(shí)現(xiàn)方式)
webview.settings?.run {
    javaScriptEnabled = true
    databaseEnabled = true
}
復(fù)制代碼

在這種情況下婴谱,顯然 T.run 擴(kuò)展函數(shù)更好,因?yàn)槲覀兛梢栽谑褂盟皩煽招赃M(jìn)行檢查躯泰。

2谭羔、this VS it 參數(shù)(This vs. it argument)

如果我們對比 T.runT.let 兩個函數(shù)也是非常的相似,唯一的區(qū)別在于它們接收的參數(shù)不一樣麦向。下面顯示了兩種功能的相同邏輯瘟裸。

stringVariable?.run {
      println("The length of this String is $length")
}
// Similarly.
stringVariable?.let {
      println("The length of this String is ${it.length}")
}
復(fù)制代碼

如果你查看過 T.run 函數(shù)聲明,你就會注意到T.run僅僅只是被當(dāng)做了 block: T.() 擴(kuò)展函數(shù)的調(diào)用塊磕蛇。因此景描,在其作用域內(nèi)十办,T 可以被 this 指代秀撇。在編碼過程中,在大多數(shù)情況下this是可以被省略的向族。因此我們上面的示例中呵燕,我們可以在println語句中直接使用 length** 而不是 **{this.lenght}. 所以我把這個稱之為傳遞 this參數(shù)

然而對于 T.let 函數(shù)的聲明,你將會注意到 T.let 是傳遞它自己本身到函數(shù)中block: (T)件相。因此這個類似于傳遞一個lambda表達(dá)式作為參數(shù)再扭。它可以在函數(shù)作用域內(nèi)部使用it來指代. 所以我把這個稱之為傳遞 it參數(shù)

從上面看,似乎T.runT.let更加優(yōu)越夜矗,因?yàn)樗[含泛范,但是T.let函數(shù)具有一些微妙的優(yōu)勢,如下所示:

  • 1紊撕、T.let函數(shù)提供了一種更清晰的區(qū)分方式去使用給定的變量函數(shù)/成員與外部類函數(shù)/成員罢荡。
  • 2、例如當(dāng)it作為函數(shù)的參數(shù)傳遞時,this不能被省略区赵,并且it寫起來比this更簡潔惭缰,更清晰。
  • 3笼才、T.let允許更好地命名已轉(zhuǎn)換的已使用變量漱受,即可以將it轉(zhuǎn)換為其他有含義名稱,而 T.run則不能骡送,內(nèi)部只能用this指代或者省略昂羡。
stringVariable?.let {
      nonNullString ->
      println("The non null string is $nonNullString")
}
復(fù)制代碼

3、返回this VS 其他類型 (Return this vs. other type)

現(xiàn)在摔踱,讓我們看看T.letT.also紧憾,如果我們看看它的內(nèi)部函數(shù)作用域,它們都是相同的昌渤。

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}")
}
復(fù)制代碼

然而赴穗,他們微妙的不同在于他們的返回值T.let返回一個不同類型的值,而T.also返回T類型本身膀息,即這個般眉。

這兩個函數(shù)對于函數(shù)的鏈?zhǔn)秸{(diào)用都很有用,其中T.let讓您演變操作潜支,而T.also則讓您對相同的變量執(zhí)行操作甸赃。

簡單的例子如下:

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
}
復(fù)制代碼

T.also似乎看上去沒有意義,因?yàn)槲覀兛梢院苋菀椎貙⑺鼈兘M合成一個功能塊冗酿。仔細(xì)思考埠对,它有一些很好的優(yōu)點(diǎn)。

  • 1裁替、它可以對相同的對象提供非常清晰的分離過程项玛,即創(chuàng)建更小的函數(shù)部分。
  • 2弱判、在使用之前襟沮,它可以非常強(qiáng)大的進(jìn)行自我操作,從而實(shí)現(xiàn)整個鏈?zhǔn)酱a的構(gòu)建操作昌腰。

當(dāng)兩者結(jié)合在一起使用時开伏,即一個自身演變,一個自我保留遭商,它能使一些操作變得更加強(qiáng)大固灵。

// 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() }
復(fù)制代碼

回顧所有屬性特征

通過回顧這3個屬性特征,我們可以非常清楚函數(shù)的行為劫流。讓我來說明T.apply函數(shù)巫玻,由于我并沒有以上函數(shù)中提到過它暑认。 T.apply的三個屬性如下

  • 1、它是一個擴(kuò)展函數(shù)
  • 2大审、它是傳遞this作為參數(shù)
  • 3蘸际、它是返回 this (即它自己本身)

因此,使用它徒扶,可以想象下它可以被用作:

// 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 }
復(fù)制代碼

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

// 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) }
復(fù)制代碼

函數(shù)的選用

因此,顯然有了這3個屬性特征姜骡,我們現(xiàn)在可以對功能進(jìn)行相應(yīng)的分類导坟。基于此圈澈,我們可以在下面構(gòu)建一個決策樹惫周,以幫助確定我們想要使用哪個函數(shù),來選擇我們需要的康栈。

[圖片上傳中...(image-2c25dd-1574061276700-1)]

<figcaption></figcaption>

[圖片上傳中...(image-f92133-1574061276700-0)]

<figcaption></figcaption>

希望上面的決策樹能夠更清晰地說明功能递递,并簡化你的決策,使你能夠適當(dāng)掌握這些功能的使用.

譯者有話說

  • 1啥么、為什么我要翻譯這篇博客?

我們都知道在Kotlin中Standard.Kt文件中短短不到100來行庫函數(shù)源碼登舞,但是它們作用是非常強(qiáng)大,可以說它們是貫穿于整個Kotlin開發(fā)編碼過程中悬荣。使用它們能讓你的代碼會更具有可讀性菠秒、更優(yōu)雅、更簡潔氯迂。善于合理使用標(biāo)準(zhǔn)庫函數(shù)践叠,也是衡量你對Kotlin掌握程度標(biāo)準(zhǔn)之一,因?yàn)槟闳タ匆恍╅_源Kotlin源碼嚼蚀,隨處可見的都是使用各種標(biāo)準(zhǔn)庫函數(shù)。

但是這些庫函數(shù)有難點(diǎn)在于它們的用法都非常相似驰坊,有的人甚至認(rèn)為有的庫函數(shù)都是多余的匾二,其實(shí)不然哮独,每個庫函數(shù)都是有它的實(shí)際應(yīng)用場景。雖然有時候你能用一種庫函數(shù)也能實(shí)現(xiàn)相同的功能舟扎,但是也許那并不是最好的實(shí)現(xiàn)方式睹限。相信很多初學(xué)者對于這些標(biāo)準(zhǔn)庫函數(shù)也是傻傻分不清楚(曾經(jīng)的我也是)羡疗,但是這篇博客非常一點(diǎn)在于它提取出了這些庫函數(shù)三個主要特征:是否是擴(kuò)展函數(shù)柳刮、是否傳遞this或it做為參數(shù)(在函數(shù)內(nèi)部表現(xiàn)就是this和it的指代)秉颗、是否需要返回調(diào)用者對象本身蚕甥,基于特征就可以進(jìn)行分類,分類后相應(yīng)的應(yīng)用場景也就一目了然敏释。這種善于提取特征思路還是值得學(xué)習(xí)的钥顽。

  • 2靠汁、關(guān)于使用標(biāo)準(zhǔn)庫函數(shù)需要補(bǔ)充的幾點(diǎn)奶浦。

第一點(diǎn): 建議盡量不要使用多個標(biāo)準(zhǔn)庫函數(shù)進(jìn)行嵌套澳叉,不要為了簡化而去做簡化,否則整個代碼可讀性會大大降低敲茄,一會是it指代遥椿,一會又是this指代家浇,估計(jì)隔一段時間后連你自己都不知道指代什么了蓝谨。

第二點(diǎn): 針對上面譯文的let函數(shù)和run函數(shù)需要補(bǔ)充下,他們之所以能夠返回其他類型的值芦昔,其原理在于內(nèi)部block lambda表達(dá)式返回的R類型,也就是這兩者函數(shù)的返回值類型取決于傳入block lambda表達(dá)式返回類型咕缎,然而決定block lambda表達(dá)式返回值類型,取決于外部傳入lambda表達(dá)式體內(nèi)最后一行返回值

第三點(diǎn): 關(guān)于T.also和T.apply函數(shù)為什么都能返回自己本身嫂伞,是因?yàn)樵诟髯訪ambda表達(dá)式內(nèi)部最后一行都調(diào)用return this,返回它們自己本身帖努,這個this能被指代調(diào)用者,是因?yàn)樗鼈兌际菙U(kuò)展函數(shù)特性

  • 3匙监、總結(jié)

關(guān)于標(biāo)準(zhǔn)庫函數(shù)本篇譯文在于告知應(yīng)用場景以及理清它們的區(qū)別以及在使用庫函數(shù)簡化代碼實(shí)現(xiàn)時要掌握好度亭姥,不要濫用否則你的代碼可讀性會很差致份,后續(xù)會深入每個標(biāo)準(zhǔn)庫函數(shù)內(nèi)部原理做分析,歡迎關(guān)注滔蝉。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蝠引,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子鸽疾,更是在濱河造成了極大的恐慌制肮,老刑警劉巖综液,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件桩了,死亡現(xiàn)場離奇詭異士葫,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來疾呻,“玉大人尉咕,你說我怎么就攤上這事。” “怎么了洲鸠?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵股囊,是天一觀的道長居灯。 經(jīng)常有香客問我,道長岩灭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任泡孩,我火速辦了婚禮变屁,結(jié)果婚禮上敞贡,老公的妹妹穿的比我還像新娘摄职。我一直安慰自己,他們只是感情好迫悠,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布鞠抑。 她就那樣靜靜地躺著忌警,像睡著了一般箕速。 火紅的嫁衣襯著肌膚如雪盐茎。 梳的紋絲不亂的頭發(fā)上字柠,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天数冬,我揣著相機(jī)與錄音,去河邊找鬼。 笑死蚂子,一個胖子當(dāng)著我的面吹牛馏谨,可吹牛的內(nèi)容都是我干的惧互。 我是一名探鬼主播喊儡,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼截珍,長吁一口氣:“原來是場噩夢啊……” “哼炸庞!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起纸颜,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎稠鼻,沒想到半個月后闺属,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體诗眨,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡峡懈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了雾狈。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片善榛。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情哪痰,我是刑警寧澤晌杰,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響反症,放射性物質(zhì)發(fā)生泄漏铅碍。R本人自食惡果不足惜呜魄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望伪很。 院中可真熱鬧,春花似錦、人聲如沸株扛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽埋嵌。三九已至了罪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背窜管。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工获搏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人聋袋。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓代嗤,卻偏偏與公主長得像姨拥,于是被迫代替她去往敵國和親凿试。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345