--- title: 'Kotlin 委託 - lateinit 懶加載、lazy 解析' disqus: kyleAlien --- Kotlin 委託 - lateinit 懶加載、lazy 解析 === ## Overview of Content 以下參考,第一行代碼 (第三版), Kotlin 進階實戰 如有引用參考本文章請詳註出處,感謝 :smile: :::success * 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/) 本篇文章對應的是 [**Kotlin 代理與懶加載機制:使用、lazy 深度解析**](https://devtechascendancy.com/kotlin-delegate_java-proxy_lateinit_lazy/) ::: [TOC] ## Java、Kotlin 代理:概述 * 在 Java 語言中沒有語言層級的委託模式,但相對應的可以透過 OOP 設計的代理模式來實現,而 Java 的代理又分為兩種 1. **靜態代理**:快速,編譯期間就完成代理 2. **動態代理**:使用反射來動態創建代理對象,消耗資源,但拓展、自由度高 兩者各有優缺點,須依照場合使用(詳情可看 [**代理模式**](https://hackmd.io/UK0V9uc9SDyLvVAyCpnuDA?view)) * Kotlin 的特別處之一在於「語言層級的代理」,也就是該篇文章要重點說明的,它包括代理類、代理屬性 > 「代理, `Proxy`」這個詞也可以替換為「委託, `Delegate`」 ### Kotlin 委託類 * Kotlin 使用「**`by` 關鍵字**」來設定委託類;以下我們來 ProxyShop 代理 RealShop 類,實現與 Java 一樣的代理模式 ```kotlin= // 1. 要有共同的接口 interface IShop { fun buy(stuffName : String) } // 2. 實作類實現接口 open class RealShop : IShop{ override fun buy(stuffName : String) { println("Real Shop buy $stuffName for you.") } } // 3. 代理類實現接口,並使用 `by` 指定代理 class ProxyShop(realShop: IShop) : IShop by realShop fun main() { val proxy = ProxyShop(RealShop()) proxy.buy("Unit test of Kotlin") } ``` :::info 委託類的模式下,可以用來「取代一般的繼承」,以上範例的功能更像是透過建構函數注入(`DI by constructor`)來完成代理 ::: * **Kotlin 委託類的另一個特點是,它也可以委託 ++多個類++(或是說委託多個介面)**,範例如下 ```kotlin= interface IShop { fun buy(stuffName : String) } open class RealShop : IShop{ override fun buy(stuffName : String) { println("Real Shop buy $stuffName for you.") } } interface IPay { fun pay(cost : Int) } class LinePay : IPay { override fun pay(cost: Int) { println("Line Pay: ${cost + 10}") } } class BankPay : IPay { override fun pay(cost: Int) { println("Bank Pay: ${cost + 20}") } } // 委託多個介面 class ProxyShop(realShop: IShop, realPay : IPay) : IShop by realShop, IPay by realPay fun main() { // 指定代理 RealShop, LinePay val line = ProxyShop(RealShop(), LinePay()) line.pay(100) line.buy("Unit test of Kotlin") // 指定代理 RealShop, BankPay val bank = ProxyShop(RealShop(), BankPay()) bank.pay(100) bank.buy("Unit test of Kotlin") } ``` > ![](https://i.imgur.com/em39vU5.png) ### Kotlin 委託屬性 * 一般來說我們在取得屬性時,會透過各自屬性的 `setter`/`getter` 來取得,在這裡我們也可以用「`by` 關鍵字」來做委託,之後的都透過委託來達成; Kotlin 委託屬性格式如下 ```shell= val/var <property 名稱>: <Type 類型> by <代理 setter/getter 類> ``` :::info * 覆寫操作符:要委託屬性就必須覆寫 `getValue`(對應 get), `setValue` (對應 set) 兩個操作符 ::: * Kotlin 的屬性委託常見的有兩種寫法,範例如下 1. **基礎委託屬性使用** ```kotlin= // 委託寄存類 class Delegate { // 委託 getter operator fun getValue(user: User, property: KProperty<*>): Any { return "getValue be call -> fieldName: ${property.name}" } // 委託 setter operator fun setValue(user: User, property: KProperty<*>, any: Any) { println("setValue be call -> fieldName: ${property.name}") } } class User { // 使用委託屬性 var name by Delegate() var password by Delegate() } fun main() { val user = User() println("${user.name}") user.name = "Kyle" println("${user.password}") user.password = "123456" } ``` > ![](https://i.imgur.com/h4yShIs.png) 2. **繼承 `ReadWriteProperty<in T, V>` 類**:表示委託的屬性是可讀寫的 ```kotlin= // 委託寄存類 class DBDelegate<in T, V>(private val field: String, private val id: Int) : // T: 要代理的類型 , V: 屬性的類型 ReadWriteProperty<T, V> { private val data = arrayOf<MutableMap<String, Any?>>( mutableMapOf( "id" to 1, "age" to 12, "name" to "Kyle", "password" to "123456" ), mutableMapOf( "id" to 2, "age" to 2000, "name" to "Alien", "password" to "654321" ) ) override fun getValue(thisRef: T, property: KProperty<*>): V { // firstOrNull 是語法糖 val cache = data.firstOrNull { it["id"] == id }?.get(field) ?: throw Exception("Cannot not find field: $field") // println("Get field of $field value, $cache") return cache as V } override fun setValue(thisRef: T, property: KProperty<*>, value: V) { // println("Set field of $field value to $value") data.firstOrNull { it["id"] == id }?.set(field, value) } } class AccountUser(id: Int) { // 要代理的類 AccountUser // 返回值 String var accountName : String by DBDelegate<AccountUser, String>("name", id) var accountAge : Int by DBDelegate<AccountUser, Int>("age", id) var accountPassword : String by DBDelegate<AccountUser, String>("password", id) } fun main() { val account = AccountUser(1) println(account.accountName) println(account.accountAge) println("origin: ${account.accountPassword}") account.accountPassword = "Apple123" println("changed: ${account.accountPassword}") } ``` :::success * 使用 `DBDelegate` 隔離數據的取得,管理數據,之後要修就不需要修改 `AccountUser` 類 ::: > ![](https://i.imgur.com/HPpwHdv.png) ## lateinit 懶加載 Kotlin 基於 Java NPE 提出空安全的概念,來避免 NPE,除非使用 `?` 符號來標示該屬性可為 null 但如果要 **自己負責屬性的狀態**,則可以使用 `lateinit`(關鍵字), `by lazy`(lambda 函數) ### 延遲化 - lateinit * **Kotlin 延遲化就使用 ==關鍵字 lateinit== 來描述該變量即可**,這樣 ^1^ 一開始就不用賦值為 null,也就不用判空,^2^ 但若是程式錯誤仍然會報錯,也就是說 **lateinit 是省略的判空,而自身負責該變量的安全** :::warning * **==基礎類型不允許使用 lateinit==** > ![](https://i.imgur.com/JTbfPl4.png) ::: 先來看看一般使用延遲,就必須使用許多的判空符號,為此必須寫下許多不必要的程式 ```kotlin= class MyClass(private var value: Int) { fun setValue(v: Int) { value = v } fun getValue() : Int? { return value } fun addValue(v: Int) { value +=v } fun reduceValue(v: Int) { value -= v } } class TestNormal { private var m : MyClass? = null // 為此機制就必需使用許多的判空 fun setMyClass(myClass: MyClass) { this.m = myClass } fun addTime(v: Int) { repeat(v) { m?.addValue(1) // 判空 m?.addValue(2) // 判空 m?.addValue(3) // 判空 m?.addValue(4) // 判空 m?.addValue(5) // 判空 } } fun reduceTime(v: Int) { repeat(v) { m?.reduceValue(1) // 判空 m?.reduceValue(2) // 判空 m?.reduceValue(3) // 判空 m?.reduceValue(4) // 判空 m?.reduceValue(5) // 判空 } } fun printValue() { println("value: ${m?.getValue()}") } } ``` 以下使用 lateinit 來描述 value 變量,就不必不斷判空,但 **++空指針必須自己負責++** ```kotlin= class TestLateInit { private lateinit var m : MyClass // lateinit 可以省去 kotlin 判空檢查 fun setMyClass(myClass: MyClass) { this.m = myClass } fun addTime(v: Int) { repeat(v) { m.addValue(1) // 判空 m.addValue(2) // 判空 m.addValue(3) // 判空 m.addValue(4) // 判空 m.addValue(5) // 判空 } } fun reduceTime(v: Int) { repeat(v) { m.reduceValue(1) // 判空 m.reduceValue(2) // 判空 m.reduceValue(3) // 判空 m.reduceValue(4) // 判空 m.reduceValue(5) // 判空 } } fun printValue() { println("value: ${m.getValue()}") } } fun main(){ val t = TestLateInit() t.setMyClass(MyClass(0)) t.addTime(3) t.printValue() } ``` **--實作結果--** > ![](https://i.imgur.com/LlotFgY.png) **--不檢查導致的空指針--** > ![](https://i.imgur.com/dcRKcQz.png) * 另外它可以使用一種特定的方式檢查,==**::<變量>.isInitialized**== 用來檢查是否已經初始化完成,當然它並不只可以檢查類的初始化,**:: 還可以檢查其它判斷** > ![](https://i.imgur.com/Mi4AWB3.png) ```kotlin= // 使用 isInitialized private lateinit var m : MyClass fun addTime(v: Int) { if(::m.isInitialized) { repeat(v) { m.addValue(1) // 判空 m.addValue(2) // 判空 m.addValue(3) // 判空 m.addValue(4) // 判空 m.addValue(5) // 判空 } } else { println("@addTime Haven't Init") } } ``` **--實作結果--** > ![](https://i.imgur.com/onwuCWK.png) ### by lazy - 單次初始化 * 使用 `by lazy{ }` 就可以懶加載,並且 **該懶加載初始化只會一次**!並且只能使用 `val` 描述 :::info 這裡的 `by` 就是使用了屬性委託機制重寫 getter ::: ```kotlin= val msg : String by lazy { println("Msg init ~~") // 只會初始化一次 "Hello kotlin by lazy" // return value } fun main() { println("first call: $msg") println("\nsecond call: $msg") } ``` > ![](https://i.imgur.com/JplX0aH.png) ### lazy 源碼分析 1. 一般常用的 lazy 源碼是使用 `SynchronizedLazyImpl` 類 ```kotlin= public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer) ``` `SynchronizedLazyImpl` 類實作源碼:其重點是 ^1.^ 使用了 `Volatile` 儲存初始化完後的值,並 ^2.^ 使用 `synchronized` 同步初始化 ```kotlin= internal object UNINITIALIZED_VALUE private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable { private var initializer: (() -> T)? = initializer @Volatile private var _value: Any? = UNINITIALIZED_VALUE // final field is required to enable safe publication of constructed instance private val lock = lock ?: this override val value: T get() { val _v1 = _value if (_v1 !== UNINITIALIZED_VALUE) { @Suppress("UNCHECKED_CAST") return _v1 as T } // 同步鎖 return synchronized(lock) { val _v2 = _value if (_v2 !== UNINITIALIZED_VALUE) { @Suppress("UNCHECKED_CAST") (_v2 as T) } else { // 執行初始化函數 val typedValue = initializer!!() _value = typedValue initializer = null typedValue } } } override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet." private fun writeReplace(): Any = InitializedLazyImpl(value) } ``` :::info **Volatile 可以保證多執行序對於屬性操作的可見性** ::: 2. 可以指定 mode,每種 mode 都也不同的特性 | mode | 說明 | | - | - | | `NONE` | 速度快,線程不安全 | | `SYNCHRONIZED` | 第一個執行的 Thread 進程初始化,之後不再初始化 | | `PUBLICATION` | 多個 Thread 可以安全訪問屬性(使用 CAS 機制) | ```kotlin= public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> = when (mode) { LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer) // 查看 SafePublicationLazyImpl LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer) LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer) } ``` `SafePublicationLazyImpl` 類實作源碼:其重點是^1.^ 使用了 `Volatile` 儲存初始化完後的值,並 ^2.^ 使用 `AtomicReferenceFieldUpdater` 執行 CAS 機制 ```kotlin= private class SafePublicationLazyImpl<out T>(initializer: () -> T) : Lazy<T>, Serializable { @Volatile private var initializer: (() -> T)? = initializer // 儲存更新的值 @Volatile private var _value: Any? = UNINITIALIZED_VALUE // this final field is required to enable safe initialization of the constructed instance private val final: Any = UNINITIALIZED_VALUE override val value: T get() { val value = _value if (value !== UNINITIALIZED_VALUE) { @Suppress("UNCHECKED_CAST") return value as T } val initializerValue = initializer // if we see null in initializer here, it means that the value is already set by another thread if (initializerValue != null) { val newValue = initializerValue() // 內部會不停循環比較 if (valueUpdater.compareAndSet(this, UNINITIALIZED_VALUE, newValue)) { initializer = null return newValue } } @Suppress("UNCHECKED_CAST") return _value as T } override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet." private fun writeReplace(): Any = InitializedLazyImpl(value) companion object { // 使用了 Java atomic 類 private val valueUpdater = java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater( SafePublicationLazyImpl::class.java, Any::class.java, "_value" ) } } ``` ## 更多的 Kotlin 語言相關文章 在這裡,我們提供了一系列豐富且深入的 Kotlin 語言相關文章,涵蓋了從基礎到進階的各個方面。讓我們一起來探索這些精彩內容! ### Kotlin 語言基礎 * **Kotlin 語言基礎**:想要建立堅實的 Kotlin 基礎?以下這些文章將帶你深入探索 Kotlin 的關鍵基礎和概念,幫你打造更堅固的 Kotlin 語言基礎 :::info * [**Kotlin 函數、類、屬性 | DataClass、Sealed、Object 關鍵字 | Enum、Companion、NPE**](https://devtechascendancy.com/kotlin-functions_oop_dataclass_sealed_object/) * [**深入探究 Kotlin 與 Java 泛型:擦除、取得泛型類型、型變、投影 | 協變、逆變**](https://devtechascendancy.com/explore-kotlin-java-generics_type_erasure/) * [**深入 Kotlin 函數特性:Inline、擴展、標準函數全解析 | 提升程式碼效能與可讀性**](https://devtechascendancy.com/kotlin_inline_extensions_standards-func/) * [**Kotlin DSL、操作符、中綴表達式 Infix | DSL 詳解 | DSL 設計與應用**](https://devtechascendancy.com/kotlin-dsl-operators-infix-explained/) ::: ### Kotlin 特性、特點 * **Kotlin 特性、特點**:探索 Kotlin 的獨特特性和功能,加深對 Kotlin 語言的理解,並增強對於語言特性的應用 :::warning * [**Kotlin 代理與懶加載機制:使用、lazy 深度解析**](https://devtechascendancy.com/kotlin-delegate_java-proxy_lateinit_lazy/) * [**Kotlin Lambda 編程 & Bytecode | Array & Collections 集合 | 集合函數式 API**](https://devtechascendancy.com/kotlin-lambda-bytecode-array-collections-functional/) * [**深入理解 Kotlin:智能推斷與 Contact 規則**](https://devtechascendancy.com/kotlin-smart-inference-contract-rules-guide/) ::: ### Kotlin 進階:協程、響應式、異步 * **Kotlin 進階:協程、響應式、異步**:若想深入學習 Kotlin 的進階主題,包括協程應用、Channel 使用、以及 Flow 的探索,請查看以下文章 :::danger * [**應用 Kotlin 協程:對比 Thread、創建協程、任務掛起 | Dispatcher、CoroutineContext、CoroutineScope**](https://devtechascendancy.com/applied-kotlin-coroutines-in-depth-guide/) * [**Kotlin Channel 使用介紹 | Select、Actor | 生產者消費者**](https://devtechascendancy.com/kotlin-channel_select_actor_cs/) * [**探索 Kotlin Flow:基本使用、RxJava 對比、背壓機制 | Flow 細節**](https://devtechascendancy.com/kotlin-flow-usage_compare-rx_backpressure/) ::: ## Appendix & FAQ :::info ::: ###### tags: `Kotlin`