Kotlin:作用域函數(shù)

Kotlin

前言

最近使用kotlin語言開發(fā)了新的項(xiàng)目,kotlin的一些特性和大量的語法糖相當(dāng)好用,相比于java,開發(fā)效率高了不少。但Kotlin大量的語法糖也帶來了一些問題:學(xué)習(xí)成本高,語法糖使用場景的困惑。
比如,當(dāng)我第一次看到作用域函數(shù)就產(chǎn)生了這樣的疑問:what is this?Which function to use?

于是我研究了一下什么是作用域函數(shù),以及各個(gè)函數(shù)的區(qū)別和使用場景。

介紹

官方介紹:The Kotlin standard library contains several functions whose sole purpose is to execute a block of code within the context of an object. When you call such a function on an object with a lambda expression provided, it forms a temporary scope. In this scope, you can access the object without its name. Such functions are called scope functions. There are five of them: let, run, with, apply, and also.

翻譯理解:作用域函數(shù)的目的是在對象的上下文中執(zhí)行代碼塊,它為調(diào)用者對象提供了一個(gè)臨時(shí)內(nèi)部作用域,在這個(gè)作用域中可以不顯式的訪問該對象。這樣的作用域函數(shù)有5個(gè):let,run,with,apply,和also。

函數(shù)

run

run函數(shù)是最能體現(xiàn)作用域的用途的函數(shù),如下使用示例:
在mian函數(shù)中使用run函數(shù)創(chuàng)建了一個(gè)單獨(dú)的作用域,在該作用域中重新定義了一個(gè)word變量,兩次打印使用的是各自作用域中的word變量,互不影響;并且,run函數(shù)返回了lambda結(jié)果。

使用示例

fun main(args: Array<String>) {
    var word = "我是小明"
    val returnValue = run {
        var word = "我是小紅"
        println("run:$word")
        word
    }
    println("main:$word")
    println("returnValue:$returnValue")
}

運(yùn)行結(jié)果:

run:我是小紅
main:我是小明
returnValue:我是小紅

with

with函數(shù)可以將任意對象作為上下文對象this傳入,并且可以隱式的訪問該對象,返回lambda結(jié)果。如下使用示例:在mian函數(shù)中使用with函數(shù)創(chuàng)建了一個(gè)臨時(shí)作用域,在該作用域中可以重新定義person變量,兩個(gè)person變量互無影響;并且可以使用this訪問上下文對象,隱式修改person的age變量值。

使用示例

data class Person (
    var name: String,
    var age: Int = 0
)
fun main(args: Array<String>) {
    var person = Person("小明",25)
    val returnValue = with(person) {
        println("with:this=$this")
        var person = Person("小紅",23)
        println("with:person=$person")
        age = 26
        person
    }
    println("main:person=$person")
    println("main:returnValue=$returnValue")
}

運(yùn)行結(jié)果:

with:this=Person(name=小明, age=25)
with:person=Person(name=小紅, age=23)
main:person=Person(name=小明, age=26)
main:returnValue=Person(name=小紅, age=23)

T.run

T.run函數(shù)可以使用T作為作用域的上下文對象this,在作用域中可以隱式訪問T對象,并返回lambda結(jié)果。

使用示例

data class Person (
    var name: String,
    var age: Int = 0
)
fun main(args: Array<String>) {
    var person: Person? = null
    // T?.run當(dāng)T為null時(shí)不調(diào)用run函數(shù)
    person?.run {
        println("person?.run:person=$person")
    }
    person = Person("小明",25)
    val returnValue = person.run {
        println("person.run:this=$this")
        var person = Person("小紅",23)
        println("person.run:person=$person")
        age = 26
        person
    }
    println("main:person=$person")
    println("main:returnValue=$returnValue")
}

運(yùn)行結(jié)果:

person.run:this=Person(name=小明, age=25)
person.run:person=Person(name=小紅, age=23)
main:person=Person(name=小明, age=26)
main:returnValue=Person(name=小紅, age=23)

T.let

T.let函數(shù)與T.run函數(shù)唯一的區(qū)別是:T作為作用域上下文對象的名稱不同,前者是it,后者是this,所以在T.let函數(shù)中必須顯式使用it訪問T對象。

使用示例

data class Person (
    var name: String,
    var age: Int = 0
)
fun main(args: Array<String>) {
    var person: Person? = null
    person?.let {
        println("person?.let:person=$person")
    }
    person = Person("小明",25)
    val returnValue = person.let {
        println("person.let:it=$it")
        var person = Person("小紅",23)
        println("person.let:person=$person")
        it.age = 26
        person
    }
    println("main:person=$person")
    println("main:returnValue=$returnValue")
}

運(yùn)行結(jié)果:

person.let:it=Person(name=小明, age=25)
person.let:person=Person(name=小紅, age=23)
main:person=Person(name=小明, age=26)
main:returnValue=Person(name=小紅, age=23)

T.also

如下使用示例,T.also函數(shù)和T.let函數(shù)的唯一區(qū)別是:前者返回值是this(即T),后者返回值是lambda結(jié)果。

使用示例

data class Person (
    var name: String,
    var age: Int = 0
)
fun main(args: Array<String>) {
    var person: Person? = null
    person?.also {
        println("person?.also:person=$person")
    }
    person = Person("小明",25)
    val returnValue = person.also {
        println("person.also:it=$it")
        var person = Person("小紅",23)
        println("person.also:person=$person")
        it.age = 26
        person
    }
    println("main:person=$person")
    println("main:returnValue=$returnValue")
}

運(yùn)行結(jié)果:

person.also:it=Person(name=小明, age=25)
person.also:person=Person(name=小紅, age=23)
main:person=Person(name=小明, age=26)
main:returnValue=Person(name=小明, age=26)

T.apply

如下使用示例,T.apply函數(shù)和T.also函數(shù)的唯一的區(qū)別是:T作為作用域上下文對象的名稱不同,前者是this,后者是it,所以在T.apply函數(shù)中可以隱式訪問T對象。

使用示例

data class Person (
    var name: String,
    var age: Int = 0
)
fun main(args: Array<String>) {
    var person: Person? = null
    person?.apply {
        println("person?.apply:person=$person")
    }
    person = Person("小明",25)
    val returnValue = person.apply {
        println("person.apply:this=$this")
        var person = Person("小紅",23)
        println("person.apply:person=$person")
        age = 26
        person
    }
    println("main:person=$person")
    println("main:returnValue=$returnValue")
}

運(yùn)行結(jié)果:

person.apply:this=Person(name=小明, age=25)
person.apply:person=Person(name=小紅, age=23)
main:person=Person(name=小明, age=26)
main:returnValue=Person(name=小明, age=26)

特殊的作用域函數(shù)

T.takeIf

以it作為在作用域上下文對象T的名稱,若lambda結(jié)果為true,返回this;否則,返回null。

函數(shù)源碼

@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
    contract {
        callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    return if (predicate(this)) this else null
}

使用示例

fun main(args: Array<String>) {
    var count = 0
    while (count <= 10) {
        val returnValue = count.takeIf {
            count++ % 2 == 0
        }
        println(returnValue)
    }
}

運(yùn)行結(jié)果:

0
null
2
null
4
null
6
null
8
null
10

T.takeUnless

以it作為在作用域上下文對象T的名稱,若lambda結(jié)果為true,返回null;否則,返回this。與taskIf的實(shí)現(xiàn)相比,其實(shí)就是對lambda結(jié)果進(jìn)行了取反操作。

函數(shù)源碼

@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? {
    contract {
        callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    return if (!predicate(this)) this else null
}

使用示例

fun main(args: Array<String>) {
    var count = 0
    while (count <= 10) {
        val returnValue = count.takeUnless {
            count++ % 2 == 0
        }
        println(returnValue)
    }
}

運(yùn)行結(jié)果:

null
1
null
3
null
5
null
7
null
9
null

repeat

以當(dāng)前執(zhí)行的次數(shù)it作為在作用域上下文對象T的名稱,執(zhí)行給定lambda函數(shù)指定的次數(shù)。從函數(shù)源碼和使用示例可以看出,執(zhí)行次數(shù)角標(biāo)是從0開始。

函數(shù)源碼

@kotlin.internal.InlineOnly
public inline fun repeat(times: Int, action: (Int) -> Unit) {
    contract { callsInPlace(action) }
    for (index in 0 until times) {
        action(index)
    }
}

使用示例

fun main(args: Array<String>) {
    repeat(5) {
        print("$it,")
    }
}

運(yùn)行結(jié)果:

0,1,2,3,4,

總結(jié)

從上面的函數(shù)介紹和實(shí)際使用可以看出let,run,with,apply,和also,這些作用域函數(shù)的功能之間起著相互補(bǔ)充的作用,單獨(dú)看某兩個(gè)函數(shù)可能差別不大,但它們結(jié)合起來所實(shí)現(xiàn)的功能涵蓋了絕大部分的使用場景。

總結(jié)一下,用于快速判斷操作符使用場景,主要使用這幾個(gè)因素辨別:

  1. 調(diào)用者

    • 正常函數(shù):有run,with函數(shù)。主要作用是:開辟一個(gè)作用域,不受作用域之外上下文影響,with還可以方便地在作用域中訪問上下文對象。
    • 擴(kuò)展函數(shù):可以使用T?.fun()在調(diào)用之前做空檢查,如:null?.run { println("Kotlin") },作用域內(nèi)容不會被執(zhí)行。
  2. 上下文對象

    • this:方便在作用域中直接訪問this
    • it:可以更清楚的區(qū)分作用域和非作用域中的成員
  3. 返回值

    • 上下文對象this:可以作為鏈?zhǔn)秸{(diào)用。
    • lambda表達(dá)式結(jié)果:返回表達(dá)式結(jié)果,可以將結(jié)果結(jié)合其他作用域函數(shù),使用更靈活。
    // 示例:使用apply函數(shù)進(jìn)行鏈?zhǔn)秸{(diào)用
    class Person {
        var name = ""
        var age = 0
    }
    fun main(args: Array<String>) {
        val person = Person().apply { name = "小明" }.apply { age = 25 }
        println("${person.name},${person.age}")
    }
    // 運(yùn)行結(jié)果:小明,25
    

下面對作用域函數(shù)簡要區(qū)分,可以更方便快速的辨別各函數(shù)的作用和使用場景。

作用域函數(shù)簡要區(qū)分:

  • run:返回lambda結(jié)果
  • with:this上下文,返回lambda結(jié)果
  • T.run:支持空檢查,this上下文,返回lambda結(jié)果
  • T.let:支持空檢查,it上下文,返回lambda結(jié)果
  • T.also:支持空檢查,it上下文,返回this(即T,it)
  • T.apply:支持空檢查,this上下文,返回this(即T,this)

特殊的作用域函數(shù)區(qū)分:

  • T.takeIf:支持空檢查,it上下文,函數(shù)體返回值類型Boolean,函數(shù)體返回true,函數(shù)返回this;否則返回null
  • T.takeUnless:支持空檢查,it上下文,函數(shù)體返回值類型Boolean,函數(shù)體返回true,函數(shù)返回null;否則返回this
  • repeat:執(zhí)行給定函數(shù) action 指定的次數(shù) times (角標(biāo):0-times)

參考資料

官方文檔:https://www.kotlincn.net/docs/reference/scope-functions.html
medium Elye:https://medium.com/@elye.project/mastering-kotlin-standard-functions-run-with-let-also-and-apply-9cd334b0ef84
CSDN george_zyf:https://blog.csdn.net/android_zyf/article/details/82496983

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

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