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

SharpCJ發表於2022-04-13

一、內聯擴充函式 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 函式裡面去處理。

相關文章