Kotlin的一些標準函式非常相似,我們不確定使用哪個函式。在這裡我將介紹一個簡單的方法來清楚地區分他們的差異和如何選擇使用。
範圍函式
我重點關注run, with, T.run, T.let, T.also and T.apply函式。我稱他們為範圍函式,因為我認為他們的主要功能是為呼叫函式提供一個內部範圍。
run函式是說明最簡單的範圍方法
fun test() {
var mood = "I am sad"
run {
val mood = "I am happy"
println(mood) // I am happy
}
println(mood) // I am sad
}
複製程式碼
有了這個函式,在test
函式內部,你可以有一個單獨的範圍,mood
在重新定義為I am happy
並列印之前,它被完全封閉在run
範圍內。
這個範圍函式本身似乎不是很有用。但是相比範圍,還有一點不錯的是,它返回範圍內最後一個物件。
因此,下面程式碼將是很純潔的,我們可以像下面一樣,將show()
方法應用到兩個 view,而不是 呼叫兩次。
run {
if (firstTimeView) introView else normalView
}.show()
複製程式碼
範圍函式的3個屬性
為了使範圍函式更有趣,讓我用3個屬性將他們的行為分類,並且使用這些屬性來區分它們。
1.正常vs.擴充套件函式
如果我們看看定義,with
並且T.run
這兩個函式實際上非常相似。下面示例實現功能是一樣的。
with(webview.settings) {
javaScriptEnabled = true
databaseEnabled = true
}
// 相似
webview.settings.run {
javaScriptEnabled = true
databaseEnabled = true
}
複製程式碼
然而,它們的不同之處在於with
是正常函式,而T.run
是擴充套件函式。
那麼問題是,每個的優點是什麼?
想象一下,如果webview.settings
可能是空的,那麼看起來就像下面一樣了。
with(webview.settings) {
this?.javaScriptEnabled = true
this?.databaseEnabled = true
}
}
webview.settings?.run {
javaScriptEnabled = true
databaseEnabled = true
}
複製程式碼
在這種情況下,顯然T.run
擴充套件功能比較好,因為在使用之前我們可以判空。
2.This vs. it引數
如果我們看看定義,,T.run
並且T.let
這兩個函式除了接受引數的方式不一樣外幾乎是一樣的。以下兩個函式的邏輯是相同的。
stringVariable?.run {
println("The length of this String is $length")
}
stringVariable?.let {
println("The length of this String is ${it.length}")
}
複製程式碼
如果你檢查T.run
函式簽名,你會注意到T.run
只是作為擴充套件函式呼叫block: T.()
。因此,所有的範圍內,T
可以被稱為this
。在程式設計中,this
大部分時間可以省略。因此,在我們上面的例子中,我們可以在println
宣告中使用$length
,而不是${this.length}
。我把這稱為傳遞this引數。
然而,對於T.let
函式簽名,你會注意到T.let
把自己作為引數傳遞進去,即block: (T)
。因此,這就像傳遞一個lambda引數。它可以在作用域範圍內使用it
作為引用。所以我把這稱為傳遞it引數。
從上面看,它似乎T.run
是更優越,因為T.let
更隱含,但是這是T.let
函式有一些微妙的優勢如下:
T.let
相比外部類函式/成員,使用給定的變數函式/成員提供了更清晰的區分- 在
this
不能被省略的情況下,例如當它作為函式的引數被傳遞時it
比this
更短,更清晰。 - 在
T.let
允許使用更好的變數命名,你可以轉換it
為其他名稱。
stringVariable?.let {
nonNullString ->
println("The non null string is $nonNullString")
}
複製程式碼
3.返回當前型別 vs.其他型別
現在,我們來看看T.let
和T.also
,如果我們看它們的內部函式範圍,使用起來是一樣的
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
。
兩者對於連結函式都是有用的,通過T.let
你可以演變操作,通過T.also
你在同一個變數this
上執行操作。
簡單的例子如下
val original = "abc"
// 改變值並且傳遞到下一鏈條
original.let {
println("The original String is $it") // "abc"
it.reversed() // 改變引數並且傳遞到下一鏈條
}.let {
println("The reverse String is $it") // "cba"
it.length // 改變型別
}.let {
println("The length of the String is $it") // 3
}
// 錯誤
// 在鏈中傳送相同的值(列印的答案是錯誤的)
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通過修改原始字串也可以達到同樣目的
// 在鏈中傳送相同的值
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
好像毫無意義,因為我們可以很容易地將它們組合成一個功能塊。但仔細想想,它也有一些優點:
- 它可以在相同的物件上提供一個非常清晰的分離過程,即製作更小的功能部分。
- 在使用之前,它可以實現非常強大的自我操縱,實現鏈條建設者操作(builder 模式)。
當兩者結合在一起時,即一個自我演變,一個自我保留,可以變得非常強大,例如下面
// 正常方法
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個屬性,我們應該可以瞭解這些函式的行為了。讓我們再來看看T.apply
函式,因為上面沒有提到。這3個屬性在T.apply
定義如下...
- 這是一個擴充套件函式
- 把
this
作為引數傳遞。 - 它返回
this
(即它本身)
因此,可以想象,它可以像下面一樣被使用
// 正常方法
fun createInstance(args: Bundle) : MyFragment {
val fragment = MyFragment()
fragment.arguments = args
return fragment
}
// 改進方法
fun createInstance(args: Bundle)
= MyFragment().apply { arguments = args }
複製程式碼
或者我們也可以建立鏈式呼叫。
// 正常方法
fun createIntent(intentData: String, intentAction: String): Intent {
val intent = Intent()
intent.action = intentAction
intent.data=Uri.parse(intentData)
return intent
}
// 改進實現
fun createIntent(intentData: String, intentAction: String) =
Intent().apply { action = intentAction }
.apply { data = Uri.parse(intentData) }
複製程式碼
函式選擇
因此,顯然,有了這三個屬性,我們現在可以對上述函式進行相應的分類。在此基礎上,我們可以在下面形成一個決策樹,可以幫助我們決定使用哪個函式。
希望上面的決策樹可以清晰說明函式區別,也簡化您的決策,使您能夠恰當掌握這些函式的使用。