一、內聯拓展函數 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 | this指代當前對象或者省略 | 返回this | 是 |
also | fun T.also(block: (T) -> Unit): T | 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
函數里面去處理。