
前言
最近使用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è)因素辨別:
-
調(diào)用者:
- 正常函數(shù):有run,with函數(shù)。主要作用是:開辟一個(gè)作用域,不受作用域之外上下文影響,with還可以方便地在作用域中訪問上下文對象。
- 擴(kuò)展函數(shù):可以使用T?.fun()在調(diào)用之前做空檢查,如:
null?.run { println("Kotlin") },作用域內(nèi)容不會被執(zhí)行。
-
上下文對象
- this:方便在作用域中直接訪問this
- it:可以更清楚的區(qū)分作用域和非作用域中的成員
-
返回值
- 上下文對象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