Kotlin 之 let、with、run、apply、also 函數的使用

一、內聯拓展函數 let

let 擴展函數的實際上是一個作用域函數,當你需要去定義一個變量在一個特定的作用域範圍內,let函數的是一個不錯的選擇;let函數另一個作用就是可以避免寫一些判斷null的操作。

1.1 let 函數的使用的一般結構

object.let {
    it.todo() //在函數體內使用it替代object對象去訪問其公有的屬性和方法
    ...
}

//另一種用途 判斷object爲null的操作
object?.let { //表示object不爲null的條件下,纔會去執行let函數體
    it.todo()
}

1.2 let函數底層的inline擴展函數+lambda結構

@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

意思就是 T 類型的對象調用 let 方法,實際調用的是傳入 let 方法的 lambda 表達式的 block 塊,最終返回 lambda 表達式的返回值。
lambda 表達式內部通過 it 指代該對象。

1.3 let 函數常見的適用的場景

  • 場景一: 最常用的場景就是使用let函數處理需要針對一個可null的對象統一做判空處理。
  • 場景二: 然後就是需要去明確一個變量所處特定的作用域範圍內可以使用
obj?.funA()
obj?.funB()
obj?.funC()

obj?.let {
    it.funA()
    it.funB()
    it.funC()
}

二、內聯函數 with

2.1 with 函數使用的一般結構

with(object) {
    //todo
}

2.2 with 函數底層的inline擴展函數+lambda 結構

@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}

注意,這個 with 函數不是拓展函數,它接收兩個參數,第一個參數是要是用的對象,第二個參數是一個 lambda 表達式,該方法實際調用的是第一個參數對象,進行 block 塊的調用,
最終返回 lambda 表達式的返回值。
lambda 表達式內部通過 this 指代該對象。

2.3 with 函數的適用的場景

適用於調用同一個類的多個方法時,可以省去類名重複,直接調用類的方法即可,經常用於Android中RecyclerView中onBinderViewHolder中,數據model的屬性映射到UI上。

obj.funA()
obj.funB()
obj.funC()

with(obj) {
    this.funA()
    funB() // this 可省略
    funC)
}

三、 內聯拓展函數 run

3.1 run 函數使用的一般結構

object.run {
    // todo
}

3.2 run 函數的inline+lambda 結構

@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

run 函數實際上可以說是let和with兩個函數的結合體,run函數只接收一個lambda函數爲參數,以閉包形式返回,即返回 lambda 表達式的返回值。

3.3 run 函數的適用場景

obj?.funA()
obj?.funB()
obj?.funC()

obj?.run {
    this.funA()
    funB() // this 可省略
    funC)
}

四、內聯拓展函數 apply

4.1 apply 函數使用的一般結構

object.apply {
    // todo
}

4.2 apply 函數的inline+lambda結構

@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

從結構上來看apply函數和run函數很像,唯一不同點就是它們各自返回的值不一樣,run函數是以閉包形式返回最後一行代碼的值,而apply函數的返回的是傳入對象的本身。

五、內聯擴展函數 also

5.1 also 函數使用的一般結構

object.also {
    // todo
}

5.2 also 函數的inline+lambda結構

public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}

also函數的結構實際上和let很像唯一的區別就是返回值的不一樣,let是以閉包的形式返回,返回函數體內最後一行的值,如果最後一行爲空就返回一個Unit類型的默認值。而also函數返回的則是傳入對象的本身。

六、比較總結

函數名 定義inline的結構 函數體內使用的對象 返回值 是否是擴展函數
let fun T.let(block: (T) -> R): R = block(this) it指代當前對象 閉包形式返回
with fun with(receiver: T, block: T.() -> R): R = receiver.block() this指代當前對象或者省略 閉包形式返回
run fun T.run(block: T.() -> R): R = block() this指代當前對象或者省略 閉包形式返回
apply fun T.apply(block: T.() -> Unit): T { block(); return this } this指代當前對象或者省略 返回this
also fun T.also(block: (T) -> Unit): T { block(this); return this } it指代當前對象 返回this

七、實用例子————Kotlin 實現單例模式

Kotlin 實現單例模式相對 java 來說很簡單。比如通過 objectby lazy 操作,相信大家都會。但有時候,我們想要在單例初始化的時候順便做一下其它初始化,極有可能會還需要傳入參數。
使用 java 時,我最喜歡的實現單例模式是靜態內部類的方式,但在 Android 中經常在初始化的時候需要傳入 context ,然後選擇了雙重檢查鎖方式。
先看 java 代碼:

public class Singleton {
    private Singleton() {
    }

    /**
     * volatile is since JDK5
     */
    private static volatile Singleton sSingleton;

    public static Singleton getInstance() {
        if (sSingleton == null) {
            synchronized (Singleton.class) {
                // 未初始化,則初始instance變量
                if (sSingleton == null) {
                    sSingleton = new Singleton();
                }
            }
        }
        return sSingleton;
    }
}

再看看我們用 kotlin 實現

class Singleton private constructor(){
    companion object {
        @Volatile
        private var instance: Singleton? = null

        fun getInstance(context: Context): Singleton {
            return instance?: synchronized(this) {
                instance?:Singleton().also {
                    instance = it
                }
            }
        }
    }
}

如果要做初始化操作,我們完全可以在 also 函數裏面去處理。

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