Kotlin使用心得(十一):lateinit vs lazy
寫在前面
本文含有翻譯自下面文章的大量內文:
lateinit:延遲初始化
來看一下我們在Android中,是如何綁定一個TextView
的:
將代碼轉換成Kotlin看看:
從代碼來看,我們必須先對textView
這個變數指定null
,進行初始化;後續在指定textView
的text
屬性時,也要先確認textView
是不為空的:
textView?.text = "hello world"
如果你能確定textView
這個變數在後續一定會被初始化,然後你也不想每次都用看起來很G8的問號來判斷變數是否為空,那你可以加個lateinit
關鍵字來修飾這個變數:
可以看到,我們再也不用事先進行初始化了,compiler也不會跟我們警告說要使用?
來判斷textView
這個變數是否為空。
上面我們使用lateinit
進行「延遲初始化」的動作,那使用這種方式有什麼好處呢?其實它非常適合用於依賴注入(Dependency Injection)的場合,讓我們來看一下:
從代碼我們可以看到,當我們要實例化一個Driver
,一定得依賴一個Vehicle
,不管這個Vehicle
是Taxi
還是Truck
。在這種情況中,Driver
中的vehicle
屬性一定不為空,所以我們也可以用lateinit
來改寫:
延遲初始化val屬性?
上面我們都是將lateinit
關鍵字加在var
關鍵字前面,那lateinit
也可以加在val
關鍵字前面嗎?不知道能不能通過編譯?讓我們來試試看:
private lateinit val textView: TextView
Compiler:
‘lateinit’ modifier is allowed only on mutable properties
翻譯:省省吧,lateinit關鍵字只能用在會改變的屬性上。
使用lateinit的注意事項
先來一段使用lateinit
進行初始化的完整代碼:
進行Decompile:
將初始化textView
(textView
使用lateinit
進行延遲初始化)的代碼片段comment
掉:
再次Decompile:
實際使用IDE去運行,可以發現:
- 在使用
lateinit
的情況下,使用lateinit
的屬性預設為null
- 使用
lateinit
後,就算你沒有初始化這個屬性,直接呼叫這個屬性的方法時,compiler也不會報錯
這可能是因為當你選擇使用lateinit
的時候,等於你告訴compiler說你在之後一定會進行初始化,而compiler也完全地相信你了,所以不多加檢查,這部分需要多加注意。
Lazy:懶加載
當你用val聲明一個變數時:
private val textView: TextView
Compiler:
Property must be initialized or be abstract
翻譯:要記得初始化啊你……
在Kotlin中,變數的初始化都必須指定值。我們先前為了避免在使用var宣告變數時就先給定初始值,使用lateinit來應變,那如何避免在使用val宣告變數時就先給定初始值呢?
那就使用lazy吧!
我們將之前的代碼改成適合使用lazy的樣式:
可以看到,lazy
的用法與委託是相同的,關鍵字前面都必須加個by
lazy
使用起來和單例模式非常的像,只有「使用lazy
初始化的物件」在「被第一次使用」時,才會執行lazy
的程式碼片段,如果沒用到,就不會執行。
在上述的代碼中,textView
什麼時候被用到呢?就是在設定textView.text
的時候。
載入這個頁面時,會收到以下的log:
I/System.out: this is lazy function block
如果不去呼叫textView.text
(或者是說不使用textView)的話,log就不會有東西print出來:
結果:
啥都沒有
如果呼叫了textView兩次,那還是只會呼叫一次lazy的function block一次,一但進行初始化後就不會再進行第二次初始化了:
結果(還是只有一行):
I/System.out: this is lazy function block
Lazy用法背後的機制
我們點進去lazy方法,看看它是怎麼實現的:
public fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
可以看到,它必須返回Lazy<T>
類型的結果,而這個結果就靠SynchronizedLazyImpl()
類別來實現:
可以看到SynchronizedLazyImpl()
類別中指定_value
為UNINITIALIZED_VALUE
,意即初始狀態為「未初始化的」。
當外部嘗試讀取value
屬性時,會先檢查_value
是否已經被初始化過,如果都還沒被初始化,才會呼叫lazy function block中的代碼。