掌握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í)it比this更短,更清晰。 - 在
T.let允許使用更好的變量命名,你可以轉(zhuǎn)換it為其他名稱。
stringVariable?.let {
nonNullString ->
println("The non null string is $nonNullString")
}
3.返回當(dāng)前類型 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。
兩者對(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):
- 它可以在相同的對(duì)象上提供一個(gè)非常清晰的分離過程,即制作更小的功能部分。
- 在使用之前,它可以實(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定義如下...
- 這是一個(gè)擴(kuò)展函數(shù)
- 把
this作為參數(shù)傳遞。 - 它返回
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ù)。
希望上面的決策樹可以清晰說明函數(shù)區(qū)別,也簡(jiǎn)化您的決策,使您能夠恰當(dāng)掌握這些函數(shù)的使用。