Kotlin的一些標準函數(shù)非常相似望几,我們不確定使用哪個函數(shù)伍掀。在這里我將介紹一個簡單的方法來清楚地區(qū)分他們的差異和如何選擇使用掰茶。
范圍函數(shù)
我重點關注run, with, T.run, T.let, T.also and T.apply函數(shù)。我稱他們?yōu)榉秶瘮?shù)蜜笤,因為我認為他們的主要功能是為調(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
}
有了這個函數(shù),在test
函數(shù)內(nèi)部把兔,你可以有一個單獨的范圍沪伙,mood
在重新定義為I am happy
并打印之前,它被完全封閉在run
范圍內(nèi)县好。
這個范圍函數(shù)本身似乎不是很有用围橡。但是相比范圍,還有一點不錯的是缕贡,它返回范圍內(nèi)最后一個對象翁授。
因此拣播,下面代碼將是很純潔的,我們可以像下面一樣收擦,將show()
方法應用到兩個 view贮配,而不是 調(diào)用兩次。
run {
if (firstTimeView) introView else normalView
}.show()
范圍函數(shù)的3個屬性
為了使范圍函數(shù)更有趣塞赂,讓我用3個屬性將他們的行為分類泪勒,并且使用這些屬性來區(qū)分它們。
1.正常vs.擴展函數(shù)
如果我們看看定義减途,with
并且T.run
這兩個函數(shù)實際上非常相似酣藻。下面示例實現(xiàn)功能是一樣的。
with(webview.settings) {
javaScriptEnabled = true
databaseEnabled = true
}
// 相似
webview.settings.run {
javaScriptEnabled = true
databaseEnabled = true
}
然而鳍置,它們的不同之處在于with
是正常函數(shù),而T.run
是擴展函數(shù)送淆。
那么問題是税产,每個的優(yōu)點是什么?
想象一下偷崩,如果webview.settings
可能是空的辟拷,那么看起來就像下面一樣了。
with(webview.settings) {
this?.javaScriptEnabled = true
this?.databaseEnabled = true
}
}
webview.settings?.run {
javaScriptEnabled = true
databaseEnabled = true
}
在這種情況下阐斜,顯然T.run
擴展功能比較好衫冻,因為在使用之前我們可以判空。
2.This vs. it參數(shù)
如果我們看看定義谒出,隅俘,T.run
并且T.let
這兩個函數(shù)除了接受參數(shù)的方式不一樣外幾乎是一樣的。以下兩個函數(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ù)簽名为居,你會注意到T.run
只是作為擴展函數(shù)調(diào)用block: T.()
。因此杀狡,所有的范圍內(nèi)蒙畴,T
可以被稱為this
。在編程中呜象,this
大部分時間可以省略膳凝。因此,在我們上面的例子中恭陡,我們可以在println
聲明中使用$length
蹬音,而不是${this.length}
。我把這稱為傳遞this參數(shù)子姜。
然而祟绊,對于T.let
函數(shù)簽名楼入,你會注意到T.let
把自己作為參數(shù)傳遞進去,即block: (T)
牧抽。因此嘉熊,這就像傳遞一個lambda參數(shù)。它可以在作用域范圍內(nèi)使用it
作為引用扬舒。所以我把這稱為傳遞it參數(shù)阐肤。
從上面看,它似乎T.run
是更優(yōu)越讲坎,因為T.let
更隱含孕惜,但是這是T.let
函數(shù)有一些微妙的優(yōu)勢如下:
-
T.let
相比外部類函數(shù)/成員,使用給定的變量函數(shù)/成員提供了更清晰的區(qū)分 - 在
this
不能被省略的情況下晨炕,例如當它作為函數(shù)的參數(shù)被傳遞時it
比this
更短衫画,更清晰。 - 在
T.let
允許使用更好的變量命名瓮栗,你可以轉(zhuǎn)換it
為其他名稱削罩。
stringVariable?.let {
nonNullString ->
println("The non null string is $nonNullString")
}
3.返回當前類型 vs.其他類型
現(xiàn)在,我們來看看T.let
和T.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
。
兩者對于鏈接函數(shù)都是有用的缨历,通過T.let
你可以演變操作以蕴,通過T.also
你在同一個變量this
上執(zhí)行操作。
簡單的例子如下
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
}
// 錯誤
// 在鏈中發(fā)送相同的值(打印的答案是錯誤的)
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通過修改原始字符串也可以達到同樣目的
// 在鏈中發(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ōu)點:
- 它可以在相同的對象上提供一個非常清晰的分離過程,即制作更小的功能部分。
- 在使用之前,它可以實現(xiàn)非常強大的自我操縱伴鳖,實現(xiàn)鏈條建設者操作(builder 模式)。
當兩者結(jié)合在一起時徙硅,即一個自我演變榜聂,一個自我保留,可以變得非常強大嗓蘑,例如下面
// 正常方法
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個屬性须肆,我們應該可以了解這些函數(shù)的行為了匿乃。讓我們再來看看T.apply
函數(shù),因為上面沒有提到豌汇。這3個屬性在T.apply
定義如下...
- 這是一個擴展函數(shù)
- 把
this
作為參數(shù)傳遞幢炸。 - 它返回
this
(即它本身)
因此,可以想象拒贱,它可以像下面一樣被使用
// 正常方法
fun createInstance(args: Bundle) : MyFragment {
val fragment = MyFragment()
fragment.arguments = args
return fragment
}
// 改進方法
fun createInstance(args: Bundle)
= MyFragment().apply { arguments = args }
或者我們也可以創(chuàng)建鏈式調(diào)用宛徊。
// 正常方法
fun createIntent(intentData: String, intentAction: String): Intent {
val intent = Intent()
intent.action = intentAction
intent.data=Uri.parse(intentData)
return intent
}
// 改進實現(xiàn)
fun createIntent(intentData: String, intentAction: String) =
Intent().apply { action = intentAction }
.apply { data = Uri.parse(intentData) }
函數(shù)選擇
因此,顯然逻澳,有了這三個屬性闸天,我們現(xiàn)在可以對上述函數(shù)進行相應的分類。在此基礎上斜做,我們可以在下面形成一個決策樹苞氮,可以幫助我們決定使用哪個函數(shù)。
希望上面的決策樹可以清晰說明函數(shù)區(qū)別陨享,也簡化您的決策葱淳,使您能夠恰當掌握這些函數(shù)的使用。