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 來說很簡單。比如通過 object
, by 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
函式裡面去處理。