詳解Kotlin中的作用域函數(shù)

Kotlin作用域函數(shù)

作用域函數(shù),Kotlin在語法層面支持拓展函數(shù),作用域函數(shù),作用域函數(shù)是指對數(shù)據(jù)做一些變換的函數(shù),與集合的操作符很相似,但集合的操作符只能作用域集合對象,而作用域函數(shù)可以操作任何對象。

Kotlin在語法層面為我們提供了: let, run, with, apply, and also幾個作用域函數(shù)

展示一個作用域函數(shù)let:

data class Person(var name: String, var age: Int, var city: String) {
    fun moveTo(newCity: String) { city = newCity }
    fun incrementAge() { age++ }
}

fun main() {
    Person("Alice", 20, "Amsterdam").let {
        println(it)
        it.moveTo("London")
        it.incrementAge()
        println(it)
    }
}

如上所示我們向let方法中傳入一個lambda表達(dá)式,值得注意的是我們在這里使用了一個it關(guān)鍵字,我們先暫時放下這個it關(guān)鍵字來試試假如不試用let方法想要獲取一樣的結(jié)果的方式

data class Person(var name: String, var age: Int, var city: String) {
    fun moveTo(newCity: String) { city = newCity }
    fun incrementAge() { age++ }
}

fun main() {
    val alice = Person("Alice", 20, "Amsterdam")
    println(alice)
    alice.moveTo("London")
    alice.incrementAge()
    println(alice)
}

可以看到在后者中我們每當(dāng)對對象進(jìn)行一次操作時,都需要傳入對象的聲明(對象名稱),而在前面的代碼中我們使用it替代了調(diào)用者Person()對象本身。當(dāng)然也可以將對象聲明為x或者任何你想要的名字

 Person("Alice", 20, "Amsterdam").let { x->
        println(x)
        x.moveTo("London")
        x.incrementAge()
        println(x)
    }
這樣看起來就跟java中的lambda表達(dá)式十分相似了。

那么問題來了,如何區(qū)分或者該在什么時候調(diào)用這些作用域函數(shù)呢,畢竟他們看起來都差不多

Kotlin官方文檔為我們提供了兩種區(qū)分方式:

  • 引用上下文的方式(it還是this)
  • 函數(shù)的返回類型

先來看看it和this的區(qū)別:

fun main() {
    val str = "Hello"
    // this
    str.run {
        println("The receiver string length: $length")
        //println("The receiver string length: ${this.length}") // does the same
        //在這段代碼中this指的是調(diào)用者本身(即lambda函數(shù)接受者)
    }

    // it
    str.let {
        println("The receiver string's length is ${it.length}")
        //這里的it值的是將調(diào)用者作為lambda函數(shù)所需參數(shù)傳入,也可以寫成
        println(x->"The receiver string's length is ${x.length}")
        
    }
}

通過上面的代碼可以得知

  • 不同:
    •  let有閉包參數(shù),run沒有閉包參數(shù)
      
    •  let的閉包參數(shù)就是調(diào)用者本身,參數(shù)名為it
      
    •  run沒有閉包參數(shù),可以使用this指代調(diào)用者
      
    •  注:在let中,不可以使用this指代調(diào)用者
      

這里列出分別使用This作為調(diào)用者以及it作為閉包參數(shù)的方法

  • this
    • run
    • with<T>
    • apply
    • 值得注意的是,大多數(shù)時候this可以被省略,但如果這樣的做法影響了代碼可讀性的話,建議使用this.
  • it
    • let
    • also

通過返回類型

  • 返回 上下文本身(指調(diào)用者)

    • apply
    • also
  • 返回lambda函數(shù)的執(zhí)行結(jié)果

    • run
    • let
    • with
  • demo1:返回上下文對象

fun main() {
    val numberList = mutableListOf<Double>()
    numberList.also { println("Populating the list") }
            .apply {
                this.add(2.71)
                add(3.14)  //省略this
                add(1.0)
            }
            .also { println("Sorting the list") }
            .sort()
    println(numberList)
}
//查看一下also與apply的源代碼
public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}
public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}

顯而易見,使用apply或者also都可以返回調(diào)用者本身,使用這樣的作用域函數(shù)可以非常方便的進(jìn)行鏈?zhǔn)骄幋a

  • dem02:返回lambda表達(dá)式結(jié)果
val numbers = mutableListOf("one", "two", "three")
    val countEndsWithE = numbers.run { 
        add("four")
        add("five")
        count { it.endsWith("e") }
    }
    println("There are $countEndsWithE elements that end with e.")
//看代碼大家都可以知道輸出結(jié)果為3
//即countEndsWithE的數(shù)據(jù)類型為Int,這是因為返回了lambda表達(dá)式中count的返回類型
public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

當(dāng)然也可以選擇無返回值(并不是沒有執(zhí)行結(jié)果)

fun main() {
    val numbers = mutableListOf("one", "two", "three")
    with(numbers) {
        val firstItem = first()
        val lastItem = last()        
        println("First item: $firstItem, last item: $lastItem")
    }
}

作用域函數(shù)的選擇

參考kotolin官方文檔:

函數(shù) 引用方式 Return value 是否為拓展函數(shù)
let it Lambda result Yes
run this Lambda result Yes
run - Lambda result No: called without the context object
with this Lambda result No: takes the context object as an argument.
apply this Context object Yes
also it Context object Yes

官方文檔也建議你選擇:

  • Executing a lambda on non-null objects(當(dāng)非空對象需要執(zhí)行閉包時): let
  • Introducing an expression as a variable in local scope(將表達(dá)式作為局部范圍中的變量引入): let
  • Object configuration(對對象進(jìn)行參數(shù)配置時): apply
  • Object configuration and computing the result(對對象進(jìn)行配置并且需要得到執(zhí)行結(jié)果): run
  • Running statements where an expression is required: non-extension(代碼運(yùn)行時需要非拓展性的表達(dá)式) run
  • Additional effects(添加附加效果): also
  • Grouping function calls on an object(對函數(shù)進(jìn)行分組調(diào)用時): with

當(dāng)然筆者也更建議讀者采用閱讀源碼+代碼實(shí)踐的方式來驗證

注意:雖然作用域函數(shù)可以使我們的代碼變得更簡潔,并且使得鏈?zhǔn)骄幊套兊酶雍唵危墙ㄗh不要在代碼中過度使用作用域函數(shù),這可能會導(dǎo)致代碼的可讀性降低,注意盡量不要使用嵌套函數(shù),并且在使用鏈?zhǔn)骄幊痰臅r候要小心混淆thisit,它們是十分容易被混淆的,而這可能會帶來十分多的問題。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容