(轉(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)
如果我們對比 with 和 T.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.run 和 T.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語句中直接使用 {this.lenght}. 所以我把這個稱之為傳遞 this參數(shù)
然而對于 T.let 函數(shù)的聲明,你將會注意到 T.let 是傳遞它自己本身到函數(shù)中block: (T)件相。因此這個類似于傳遞一個lambda表達(dá)式作為參數(shù)再扭。它可以在函數(shù)作用域內(nèi)部使用it來指代. 所以我把這個稱之為傳遞 it參數(shù)
從上面看,似乎T.run比T.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.let和T.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)注滔蝉。