---
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")
}
```
> 
### 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"
}
```
> 
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` 類
:::
> 
## lateinit 懶加載
Kotlin 基於 Java NPE 提出空安全的概念,來避免 NPE,除非使用 `?` 符號來標示該屬性可為 null
但如果要 **自己負責屬性的狀態**,則可以使用 `lateinit`(關鍵字), `by lazy`(lambda 函數)
### 延遲化 - lateinit
* **Kotlin 延遲化就使用 ==關鍵字 lateinit== 來描述該變量即可**,這樣 ^1^ 一開始就不用賦值為 null,也就不用判空,^2^ 但若是程式錯誤仍然會報錯,也就是說 **lateinit 是省略的判空,而自身負責該變量的安全**
:::warning
* **==基礎類型不允許使用 lateinit==**
> 
:::
先來看看一般使用延遲,就必須使用許多的判空符號,為此必須寫下許多不必要的程式
```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()
}
```
**--實作結果--**
> 
**--不檢查導致的空指針--**
> 
* 另外它可以使用一種特定的方式檢查,==**::<變量>.isInitialized**== 用來檢查是否已經初始化完成,當然它並不只可以檢查類的初始化,**:: 還可以檢查其它判斷**
> 
```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")
}
}
```
**--實作結果--**
> 
### 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")
}
```
> 
### 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`