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ù)名為itrun沒有閉包參數(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候要小心混淆this和it,它們是十分容易被混淆的,而這可能會帶來十分多的問題。