原文連接java
Kotlin的一些標準函數很是類似,咱們不肯定使用哪一個函數。在這裏我將介紹一個簡單的方法來清楚地區分他們的差別和如何選擇使用。git
我重點關注run, with, T.run, T.let, T.also and T.apply函數。我稱他們爲範圍函數,由於我認爲他們的主要功能是爲調用函數提供一個內部範圍。github
run函數是說明最簡單的範圍方法web
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
範圍內。編程
這個範圍函數自己彷佛不是頗有用。可是相比範圍,還有一點不錯的是,它返回範圍內最後一個對象。app
所以,下面代碼將是很純潔的,咱們能夠像下面同樣,將show()
方法應用到兩個 view,而不是 調用兩次。函數
run {
if (firstTimeView) introView else normalView
}.show()
複製代碼
爲了使範圍函數更有趣,讓我用3個屬性將他們的行爲分類,而且使用這些屬性來區分它們。ui
若是咱們看看定義,with
而且T.run
這兩個函數實際上很是類似。下面示例實現功能是同樣的。this
with(webview.settings) {
javaScriptEnabled = true
databaseEnabled = true
}
// 類似
webview.settings.run {
javaScriptEnabled = true
databaseEnabled = true
}
複製代碼
然而,它們的不一樣之處在於with
是正常函數,而T.run
是擴展函數。spa
那麼問題是,每一個的優勢是什麼?
想象一下,若是webview.settings
多是空的,那麼看起來就像下面同樣了。
with(webview.settings) {
this?.javaScriptEnabled = true
this?.databaseEnabled = true
}
}
webview.settings?.run {
javaScriptEnabled = true
databaseEnabled = true
}
複製代碼
在這種狀況下,顯然T.run
擴展功能比較好,由於在使用以前咱們能夠判空。
若是咱們看看定義,,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")
}
複製代碼
如今,咱們來看看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
好像毫無心義,由於咱們能夠很容易地將它們組合成一個功能塊。但仔細想一想,它也有一些優勢:
當二者結合在一塊兒時,即一個自我演變,一個自我保留,能夠變得很是強大,例以下面
// 正常方法
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) }
複製代碼
所以,顯然,有了這三個屬性,咱們如今能夠對上述函數進行相應的分類。在此基礎上,咱們能夠在下面造成一個決策樹,能夠幫助咱們決定使用哪一個函數。
但願上面的決策樹能夠清晰說明函數區別,也簡化您的決策,使您可以恰當掌握這些函數的使用。