翻譯說明:java
原標題: Mastering Kotlin standard functions: run, with, let, also and applygit
原文地址: medium.com/@elye.proje…github
原文做者: Elyeweb
Kotlin 有一些類似的標準函數,咱們不肯定該使用哪一種函數。接下來,我將介紹一種簡單的方法來清楚地區分它們的差別以及如何選擇使用。編程
我會專一於函數的 run,with,T.run,T.let,T.also 和 T.apply。我稱他們爲做用域函數,我將它們的主要功能視爲:爲其調用者函數提供的內部做用域。bash
說明範圍中最簡單方法是運行函數app
fun test() {
var mood = "I am sad"
run {
val mood = "I am happy"
println(mood) // I am happy
}
println(mood) // I am sad
}
複製代碼
有了這個,在測試函數內部,你能夠有一個單獨的範圍,在打印以前 mood 從新定義 I am happy,而且它徹底包含在運行範圍內。less
這個做用域函數自己彷佛不是頗有用。但它有另外一個好處,而不只僅是範圍;它返回一些東西,即範圍內的最後一個對象。函數
所以,下面將是整潔的,咱們能夠將 show() 兩個視圖應用於下面,而不是兩次調用它。post
run {
if (firstTimeView) introView else normalView
}.show()
複製代碼
爲了使範圍函數更有趣,讓我用3個屬性對它們的行爲進行分類。我將使用這些屬性來區分它們。
若是咱們看一下 with 和 T.run,這兩個函數其實是至關相似的。如下是一樣的事情。
with(webview.settings) {
javaScriptEnabled = true
databaseEnabled = true
}
// similarly
webview.settings.run {
javaScriptEnabled = true
databaseEnabled = true
}
複製代碼
然而,它們的不一樣之處在於一個是標準函數,即 with 另外一個是擴展函數 T.run。
因此問題是,每一個的優點是什麼?
想象一下,若是 webview.settings 可能爲 null,它們將以下所示。
// Yack!
with(webview.settings) {
this?.javaScriptEnabled = true
this?.databaseEnabled = true
}
// Nice.
webview.settings?.run {
javaScriptEnabled = true
databaseEnabled = true
}
複製代碼
在這種狀況下,明確的 T.run 擴展函數更好,由於咱們能夠在使用以前應用檢查可空性。
If we look at T.run and T.let, both functions are similar except for one thing, the way they accept the argument. The below shows the same logic for both functions.
若是咱們看一下 T.run 和 T.let,除了一件事倆個函數都是類似的,他們接受實際參數的方式相似。如下顯示了倆種邏輯相同的函數。
stringVariable?.run {
println("The length of this String is $length")
}
// Similarly.
stringVariable?.let {
println("The length of this String is ${it.length}")
}
複製代碼
若是你檢查 T.run 函數簽名,你會發現 T.run 它只是做爲擴展函數調用 block: T.()。所以,在範圍內,T 能夠稱爲 this。在編程中,this 大多數時候能夠省略。所以,在上面的示例中,咱們能夠 {length} 在 println 語句中使用,而不是 {this.length}。我稱之爲發送此做爲參數。
可是對於 T.let 函數簽名,您會注意到它 T.let 正在將自身發送到函數中,即 block: (T)。所以,這就像發送它的 lambda 實際參數。它能夠在範圍函數中引用爲 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}")
}
// Exactly the same as below
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"
// 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
}
複製代碼
在 T.also 彷佛毫無心義上面,由於咱們能夠很容易地將它們組合成一個單獨的函數。仔細思考,它有一些很好的優點。
它能夠在相同的物體上提供很是清晰的分離過程,即製做較小的函數部分。
在使用以前,它能夠很是強大的自我操做,使連接構建器操做。
當倆者結合起來是,一個繼續演化,一個保持不變,它變得更強,例以下面
// 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() }
複製代碼
經過查看3個屬性,咱們幾乎能夠知道函數行爲。讓我說明一下這個 T.apply 函數,由於它沒有在上面提到過。3個屬性 T.apply 以下......
所以,使用它,能夠想象,它能夠用做
// 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 }
複製代碼
或者咱們也能夠建立鏈式對象。
// 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) }
複製代碼
所以很明顯,有了3個屬性,咱們如今能夠相應地對函數進行分類。基於此,咱們能夠在下面造成一個決策樹,能夠幫助決定咱們想要使用的功能等待咱們須要的功能。
但願上面的決策樹可以更清楚地說明函數,並簡化您的決策,使您可以恰當地掌握這些函數的使用。
隨意提供一些很好的實例,說明如何使用這些函數做爲對此博客的迴應。我很樂意聽到你的消息。這可能有益於其餘人。
我但願你感謝這篇文章,它對你有所幫助。與他人分享。
你能夠在這裏查看個人其餘有趣話題。
在 Medium, Twitter 或Facebook上關注我,獲取有關Android,Kotlin等相關主題的小技巧和知識。〜Elye〜
歡迎關注 Kotlin 中文社區!
中文官網:www.kotlincn.net/
中文官方博客:www.kotliner.cn/
公衆號:Kotlin
知乎專欄:Kotlin
CSDN:Kotlin中文社區
掘金:Kotlin中文社區
簡書:Kotlin中文社區