簡介Kotlin let,run,also,apply,with等幾種常用的拓展函數

cover.png

Kotlin的Standard Library提供了幾種拓展函數(extension function),有的可以更優雅處理可空變量問題。

相信在學習 Kotlin 時,一定都會注意到它有許多let apply run 等這些 extension function,它們看起來大同小異,用起來似乎也經常可以相互替換,稍做修改就讓程式依照正確的邏輯執行。

function identifier return
Let it 最後一行
run This 最後一行
also it this
apply this this

常用

T.let

T是什麼?

在Kotlin extension function中需要接在一個變數後面才行。像是 someVariable.run { /* do something */ },包含 T.run 下面四個 let also apply 都屬於這種 extension function。因爲 run有兩種用法,這裏爲了避免混淆而將 T 寫出來。

T.let 默認當前這個對象作爲閉包的it參數,返回值是函數裏面最後一行,或者指定return@let

private var name: String? = null

@JvmStatic
fun main(args: Array<String>?) {
    name = let {
        return@let "wazing"
    }
    name?.let {
        println("my name is $it")
    }
}

結果: my name is wazing

  1. name.let{} 如果name爲null,let{}中的程式碼還是會被執行,只不過此時itnull;
  2. name?.let{} 如果 name爲null,let{} 中的程式碼不會執行;
  3. T 在 scope 內則是用 it 來存取而不是 this。也可以依照需求改成其他的名字,增加可讀性;
  4. T.let{}會作爲下一個擴展函數或者回傳給之前的變數。

Run

@JvmStatic
fun main(args: Array<String>?) {
    val name: String = run {
        val sb = StringBuilder()
        return@run sb.let { sb ->
            sb.append("wa")
        }.apply {
            append("zing")
        }.run {
            return@run toString()
        }
    }
    println("my name is $name")
}

結果: my name is wazing

同樣如果傳進來的變數是空值,T.run{} 內的程式碼就根本不會執行了。除此之外, T.runrun 的特性完全一樣。可以將最後一行的結果回傳,或是作爲下個extension function。

TT.run{}T.let的scope內相反,T.run{}是用this而不是 it

also

可以寫作T.also

與上面的runlet的不同之處在於: runlet會將最後一行傳給下個extension function或是回傳;alsoapply則是將「自己(this)」回傳或傳入下個extension function。

@JvmStatic
fun main(args: Array<String>?) {
    val fruits: ArrayList<String> = ArrayList<String>().also {
        it.add("蘋果🍎")
    }.also {
        it.add("梨子🍐")
        it.add("菠蘿🍍")
    }.apply {
        add("西瓜🍉")
    }
    println("$fruits")
}

結果: [蘋果🍎, 梨子🍐, 菠蘿🍍, 西瓜🍉]

apply

可以寫作T.apply

applyalso相似,不同的地方是apply在 scope內T的存取方式是this,其他都與also`一樣。

@JvmStatic
fun main(args: Array<String>?) {
    val list: ArrayList<String> = ArrayList<String>().apply {
        add("Android")
        add("iOS")
        add("HongmengOS")
    }
    println(list.toString())
}

結果: [Android, iOS, HongmengOS]

with

with一般常常作爲初始化時使用,with(T)之中的傳入值可以以thisscope中取用;

@JvmStatic
fun main(args: Array<String>?) {
    with(ArrayList<String>()){
        this.add("🍎")
        this.add("🍌")
        add("🍅")
        add("🌰")
        remove("🍌")
        println("$size")
    }
}

結果: 3

但很多使用狀況變數可能是可爲空的變數,如此一來withscope中就必須要宣告 ?!!來取用該物件的方法 (Method)。

@JvmStatic
fun main(args: Array<String>?) {
    var list: ArrayList<String>? = null
    with(list) {
        this?.add("🍎")
        this?.add("🍌")
        this?.add("🍅")
        this?.add("🌰")
        this?.remove("🍌")
        println("${this?.size}")
    }
}

結果: null

repeat

以下爲爲源碼,Standard.kt

@kotlin.internal.InlineOnly
public inline fun repeat(times: Int, action: (Int) -> Unit) {
    contract { callsInPlace(action) }

    for (index in 0 until times) {
        action(index)
    }
}

repeat函數是一個單獨的函數,循環執行[times]次[action]中內容。

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
}

滿足[predicate]中條件,則返回當前值[T],否則返回null,[predicate]的返回值Boolean類型

@JvmStatic
fun main(args: Array<String>?) {
    val name = 1.takeIf { it == 1 }
    println(name)
}

結果: 1

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相反,滿足[predicate]中條件,則返回null,否則返回當前值[T],[predicate]的返回值Boolean類型

@JvmStatic
fun main(args: Array<String>?) {
    val name = 1.takeUnless { it == 1 }
    println(name)
}

結果: null

結尾

Standard Library Function 其實可以互相替換,選擇合適的場景使用即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章