前言
最近使用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è)因素辨別:
-
調(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í)行搀捷。
-
上下文對(duì)象
- this:方便在作用域中直接訪問(wèn)this
- it:可以更清楚的區(qū)分作用域和非作用域中的成員
-
返回值
- 上下文對(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