Kotlin標準庫中提供了一套用於常用操作的函式。最近,在我的Kotlin交流群中有人再次問到了關於這些函式的用法。今天,讓我們花一點時間,一起看一下這些函式的用法。
Ready go >>>
注:這裡所說的標準函式主要來自於標準庫中在檔案Standard.kt中的所有函式。
run#1
@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
複製程式碼
contract部分主要用於編譯器上下文推斷,這裡我們忽略掉這部分程式碼。
觀察原始碼發現,run方法僅僅是執行傳入的block表示式並返回執行結果而已(block是一個lambda表示式)。
因此,如果你僅僅需要執行一個程式碼塊,可以使用該函式
看一個例子:
val x = run {
println("Hello, world")
return@run 1
}
println(x)
// 執行結果
Hello,world
1
複製程式碼
run#2
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
複製程式碼
這個函式跟上面的函式功能是完全一樣的。不同的是,block的receiver是當前呼叫物件,即在block中可以使用當前物件的上下文。
因此,如果你需要在執行的lambda表示式中使用當前物件的上下文的話,可以使用該函式。除此之外,兩者沒有任何差別
看一個例子:
class A {
fun sayHi(name: String) {
println("Hello, $name")
}
}
class B {
}
fun main(args: Array<String>) {
val a = A()
val b = a.run {
// 這裡你可以使用A的上下文
a.sayHi("Scott Smith")
return@run B()
}
println(b)
}
// 執行結果
Hello,Scott Smith
b@2314
複製程式碼
從例子中,我們可以看到,這個函式還可以用於對資料型別進行轉換。
with
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return receiver.block()
}
複製程式碼
這個函式其實和run函式也是做了一樣的事情。不同的是,這裡可以指定block的接收者。
因此,如果你在執行lambda表示式的時候,希望指定不同的接收者的話,可以使用該方法
class A {
fun sayHi(name: String) {
println("Hello, $name")
}
}
fun main(args: Array<String>) {
val a = A()
with(a) {
// 這裡的接收者是物件a,因此可以呼叫a例項的所有方法
sayHi("Scott Smith")
}
}
複製程式碼
apply
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
複製程式碼
可以看到,這個方法是針對泛型引數的擴充套件方法,即所有物件都將擁有該擴充套件方法。相對於run#2方法,apply不僅執行了block,同時還返回了receiver本身。
這在鏈式程式設計中很常用,如果你希望執行lambda表示式的同時而不破壞鏈式程式設計,可以使用該方法
看一個例子:
class A {
fun sayHi(name: String) {
println("Hello, $name")
}
fun other() {
println("Other function...")
}
}
fun main(args: Array<String>) {
val a = A()
a.apply {
println("This is a block")
sayHi("Scott Smith")
}.other()
}
// 執行結果
This is a block
Hello, Scott Smith
Other function...
複製程式碼
also
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block(this)
return this
}
複製程式碼
這個函式跟with又很像,不同的是,block帶有一個當前receiver型別的引數。在block中,你可以使用該引數對當前例項進行操作。
這個函式和with完全可以互相通用,with函式可以直接在當前例項上下文中對其進行操作,而also函式要通過block引數獲取當前類例項。因為用法完全一致,這裡就不舉例了
let
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
複製程式碼
如果你使用過RxJava,可能會感到似曾相識,這其實就是RxJava的map函式。這個函式也是針對泛型引數的擴充套件函式,所有類都將擁有這個擴充套件函式。
如果你希望對當前資料型別進行一定的轉換,可以使用該方法。該方法的block中同樣可以使用當前receiver的上下文
看一個例子:
class Triangle {}
class Rectangle {}
fun main(args: Array<String>) {
val tr = Triangle()
val rect = tr.let { it ->
println("It is $it")
return@let Rectangle()
}
println(rect)
}
// 執行結果
It is Triangle@78308db1
Rectangle@27c170f0
複製程式碼
從例子中可以看到,我們成功地將三角形轉換成了矩形,這就是let函式的作用。
takeIf
@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>) {
val arr = listOf(1, 2, 3)
arr.forEach {
println("$it % 2 == 0 => ${it.takeIf { it % 2 == 0 }}")
}
}
// 執行結果
1 % 2 == 0 => null
2 % 2 == 0 => 2
3 % 2 == 0 => null
複製程式碼
takeUnless
@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
}
複製程式碼
這個函式剛好與takeIf篩選邏輯恰好相反。即:如果符合條件返回null,不符合條件返回物件本身。
看一個例子:
fun main(args: Array<String>) {
val arr = listOf(1, 2, 3)
arr.forEach {
println("$it % 2 == 0 => ${it.takeUnless { it % 2 == 0 }}")
}
}
// 執行結果
1 % 2 == 0 => 1
2 % 2 == 0 => null
3 % 2 == 0 => 3
複製程式碼
看到了嗎?這裡的執行結果和takeIf恰好相反。
repeat
@kotlin.internal.InlineOnly
public inline fun repeat(times: Int, action: (Int) -> Unit) {
contract { callsInPlace(action) }
for (index in 0 until times) {
action(index)
}
}
複製程式碼
這個函式意思很明顯,就是將一個動作重複指定的次數。動作對應一個lambda表示式,表示式中持有一個參數列示當前正在執行的次數索引。
看一個例子:
fun main(args: Array<String>) {
repeat(3) {
println("Just repeat, index: $it")
}
}
Just repeat, index: 0
Just repeat, index: 1
Just repeat, index: 2
複製程式碼
簡單總結
最後,我們用一個表格簡單總結一下這些函式的用法:
函式 | 用途 | 特點 | 形式 |
---|---|---|---|
run#1 | 執行block,並返回執行結果 | block中無法獲取接收者上下文 | 全域性函式 |
run#2 | 執行block,並返回執行結果 | block中可以獲取接收者上下文 | 擴充套件函式 |
with | 指定接收者,通過接收者執行block | block中可以獲取接收者的上下文,可以對接收者資料型別做一定轉換 | 全域性函式 |
apply | 執行block,並返回接收者例項本身 | block中可以獲取接收者的上下文,可用於鏈式程式設計 | 擴充套件 |
also | 執行block,並返回接收者例項本身 | block中有一個引數代表接收者例項,可用於鏈式程式設計 | 擴充套件 |
let | 執行block,並返回執行結果 | block中有一個引數代表接收者例項,可以對接收者資料型別做一定轉換 | 擴充套件 |
takeIf | 根據條件predicate判斷當前例項是否符合要求 | 如果符合要求,返回當前例項本身;否則返回null | 擴充套件函式 |
takeUnless | 根據條件predicate判斷當前例項是否不符合要求 | 如果不符合要求,返回當前例項本身;否則返回null | 擴充套件 |
搞定Receiver
理解上面這幾個函式,最重要的一點是要理解Receiver。遺憾的是,Kotlin官方文件中並沒有針對Receiver的詳細講解。關於這部分的講解,請掃描下方二維碼關注歐陽鋒工作室,回覆搞定Receiver檢視文章。
歡迎加入Kotlin交流群
關於Kotlin,如果你有任何問題,歡迎加入我的Kotlin交流群: 329673958。當前群交流活躍,問題解答速度很快,期待你的加入。