Kotlin:作用域函數(shù)

Kotlin

前言

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

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

介紹

官方介紹: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ù)的目的是在對(duì)象的上下文中執(zhí)行代碼塊奕删,它為調(diào)用者對(duì)象提供了一個(gè)臨時(shí)內(nèi)部作用域俺泣,在這個(gè)作用域中可以不顯式的訪問(wèn)該對(duì)象。這樣的作用域函數(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ù)可以將任意對(duì)象作為上下文對(duì)象this傳入刊愚,并且可以隱式的訪問(wèn)該對(duì)象横朋,返回lambda結(jié)果。如下使用示例:在mian函數(shù)中使用with函數(shù)創(chuàng)建了一個(gè)臨時(shí)作用域百拓,在該作用域中可以重新定義person變量,兩個(gè)person變量互無(wú)影響晰甚;并且可以使用this訪問(wèn)上下文對(duì)象衙传,隱式修改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作為作用域的上下文對(duì)象this厕九,在作用域中可以隱式訪問(wèn)T對(duì)象蓖捶,并返回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作為作用域上下文對(duì)象的名稱(chēng)不同扁远,前者是it俊鱼,后者是this刻像,所以在T.let函數(shù)中必須顯式使用it訪問(wèn)T對(duì)象。

使用示例

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作為作用域上下文對(duì)象的名稱(chēng)不同溜徙,前者是this,后者是it犀填,所以在T.apply函數(shù)中可以隱式訪問(wèn)T對(duì)象蠢壹。

使用示例

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作為在作用域上下文對(duì)象T的名稱(chēng),若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作為在作用域上下文對(duì)象T的名稱(chēng)疏日,若lambda結(jié)果為true,返回null佳窑;否則制恍,返回this。與taskIf的實(shí)現(xiàn)相比神凑,其實(shí)就是對(duì)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作為在作用域上下文對(duì)象T的名稱(chēng),執(zhí)行給定lambda函數(shù)指定的次數(shù)溉委。從函數(shù)源碼和使用示例可以看出鹃唯,執(zhí)行次數(shù)角標(biāo)是從0開(kāi)始。

函數(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é)合起來(lái)所實(shí)現(xiàn)的功能涵蓋了絕大部分的使用場(chǎng)景熄求。

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

  1. 調(diào)用者

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

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

    • 上下文對(duì)象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
    

下面對(duì)作用域函數(shù)簡(jiǎn)要區(qū)分便斥,可以更方便快速的辨別各函數(shù)的作用和使用場(chǎng)景至壤。

作用域函數(shù)簡(jiǎn)要區(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ù)體返回值類(lèi)型Boolean恋捆,函數(shù)體返回true照皆,函數(shù)返回this;否則返回null
  • T.takeUnless:支持空檢查沸停,it上下文膜毁,函數(shù)體返回值類(lèi)型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)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市能颁,隨后出現(xiàn)的幾起案子杂瘸,更是在濱河造成了極大的恐慌,老刑警劉巖劲装,帶你破解...
    沈念sama閱讀 223,002評(píng)論 6 519
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡占业,警方通過(guò)查閱死者的電腦和手機(jī)绒怨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,357評(píng)論 3 400
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)谦疾,“玉大人南蹂,你說(shuō)我怎么就攤上這事∧罨校” “怎么了六剥?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,787評(píng)論 0 365
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)峰伙。 經(jīng)常有香客問(wèn)我疗疟,道長(zhǎng),這世上最難降的妖魔是什么瞳氓? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,237評(píng)論 1 300
  • 正文 為了忘掉前任策彤,我火速辦了婚禮,結(jié)果婚禮上匣摘,老公的妹妹穿的比我還像新娘店诗。我一直安慰自己,他們只是感情好音榜,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,237評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布庞瘸。 她就那樣靜靜地躺著,像睡著了一般赠叼。 火紅的嫁衣襯著肌膚如雪擦囊。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,821評(píng)論 1 314
  • 那天梅割,我揣著相機(jī)與錄音霜第,去河邊找鬼。 笑死户辞,一個(gè)胖子當(dāng)著我的面吹牛泌类,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播底燎,決...
    沈念sama閱讀 41,236評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼刃榨,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了双仍?” 一聲冷哼從身側(cè)響起枢希,我...
    開(kāi)封第一講書(shū)人閱讀 40,196評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎朱沃,沒(méi)想到半個(gè)月后苞轿,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體茅诱,經(jīng)...
    沈念sama閱讀 46,716評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,794評(píng)論 3 343
  • 正文 我和宋清朗相戀三年搬卒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瑟俭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,928評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡契邀,死狀恐怖摆寄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情坯门,我是刑警寧澤微饥,帶...
    沈念sama閱讀 36,583評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站古戴,受9級(jí)特大地震影響欠橘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜允瞧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,264評(píng)論 3 336
  • 文/蒙蒙 一简软、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧述暂,春花似錦痹升、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,755評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至艺配,卻和暖如春察郁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背转唉。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,869評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工皮钠, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人赠法。 一個(gè)月前我還...
    沈念sama閱讀 49,378評(píng)論 3 379
  • 正文 我出身青樓麦轰,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親砖织。 傳聞我的和親對(duì)象是個(gè)殘疾皇子款侵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,937評(píng)論 2 361