Kotlin對象的懶加載方式by?lazy?與?lateinit?異同詳解
前言
屬性或?qū)ο蟮难訒r加載是我們相當常用的,一般我們都是使用 lateinit 和 by lazy 來實現(xiàn)。
他們兩者都是延時初始化,那么在使用時那么他們兩者有什么區(qū)別呢?
lateinit
見名知意,延時初始化的標記。lateinit var可以讓我們聲明一個變量并且不用馬上初始化,在我們需要的時候進行手動初始化即可。
如果我們不初始化會怎樣?
private lateinit var name: String findViewById<Button>(R.id.btn_load).click { YYLogUtils.w("name:$name age:$age") }
會報錯:
所以對應(yīng)這一種情況我們會有一個是否初始化的判斷
private lateinit var name: String findViewById<Button>(R.id.btn_load).click { if (this::name.isInitialized) { YYLogUtils.w("name:$name age:$age") } }
lateinit var的作用相對較簡單,其實就是讓編譯期在檢查時不要因為屬性變量未被初始化而報錯。(注意一定要記得初始化哦?。?/p>
by lazy
by lazy 委托延時處理,分為委托和延時
其實如果我們不想延時初始化,我們直接使用委托by也可以實現(xiàn)。
private var age: Int by Delegates.observable(18) { property, oldValue, newValue -> YYLogUtils.w("發(fā)生了回調(diào) property:$property oldValue:$oldValue newValue:$newValue") } findViewById<Button>(R.id.btn_load).click { age = 25 YYLogUtils.w("name:$name age:$age") }
我們通過 by Delegates 的方式就可以指定委托對象,這里我用的 Delegates.obsevable 它的作用是修改 age 的值之后會有回調(diào)的處理。
運行的效果:
除了 Delegates.obsevable 它還有其他的用法。
public object Delegates { public fun <T : Any> notNull(): ReadWriteProperty<Any?, T> = NotNullVar() public inline fun <T> observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit): ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) { override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue) } public inline fun <T> vetoable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Boolean): ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) { override fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = onChange(property, oldValue, newValue) } } private class NotNullVar<T : Any>() : ReadWriteProperty<Any?, T> { private var value: T? = null public override fun getValue(thisRef: Any?, property: KProperty<*>): T { return value ?: throw IllegalStateException("Property ${property.name} should be initialized before get.") } public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { this.value = value } }
- notNull方法我們可以看到就是說這個對象不能為null,否則就會拋出異常。
- observable方法主要用于監(jiān)控屬性值發(fā)生變更,類似于一個觀察者。當屬性值被修改后會往外部拋出一個變更的回調(diào)。
- vetoable方法跟observable類似,都是用于監(jiān)控屬性值發(fā)生變更,當屬性值被修改后會往外部拋出一個變更的回調(diào)。與observable不同的是這個回調(diào)會返回一個Boolean值,來決定此次屬性值是否執(zhí)行修改。
其實用不用委托沒什么區(qū)別,就是看是否需要屬性變化的回調(diào)監(jiān)聽,否則我們直接用變量即可
private var age: Int = 18 findViewById<Button>(R.id.btn_load).click { age = 25 YYLogUtils.w("name:$name age:$age") }
如果我們想實現(xiàn)延時初始化的關(guān)鍵就是 lazy 關(guān)鍵字,所以,lazy是如何工作的呢? 讓我們一起在Kotlin標準庫參考中總結(jié)lazy()方法,如下所示:
- lazy() 返回的是一個存儲在lambda初始化器中的Lazy類型實例。
- getter的第一次調(diào)用執(zhí)行傳遞給lazy()的lambda并存儲其結(jié)果。
- 后面再調(diào)用的話,getter調(diào)用只返回存儲中的值。
簡單地說,lazy創(chuàng)建一個實例,在第一次訪問屬性值時執(zhí)行初始化,存儲結(jié)果并返回存儲的值。
private val age: Int by lazy { 18 / 2 } findViewById<Button>(R.id.btn_load).click { age = 25 YYLogUtils.w("name:$name age:$age") }
由于我們使用的是 by lazy ,歸根到底還是一種委托,只是它是一種特殊的委托,它的過程是這樣的:
我們的屬性 age 需要 by lazy 時,它生成一個該屬性的附加屬性:age?delegate。 在構(gòu)造器中,將使用 lazy(()->T) 創(chuàng)建的 Lazy 實例對象賦值給 age?delegate。 當該屬性被調(diào)用,即其getter方法被調(diào)用時返回 age?delegate.getVaule(),而 age?delegate.getVaule()方法的返回結(jié)果是對象 age?delegate 內(nèi)部的 _value 屬性值,在getVaule()第一次被調(diào)用時會將_value進行初始化并儲存起來,往后都是直接將_value的值返回,從而實現(xiàn)屬性值的唯一一次的初始化,并無法再次修改。所以它是只讀的。
當我們調(diào)用這個 age 這個屬性的時候才會初始化,它屬于一種懶加載,既然是懶加載,就必然涉及到線程安全的問題,我們看看lazy是怎么解決的。
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer) public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> = when (mode) { LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer) LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer) LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer) } public actual fun <T> lazy(lock: Any?, initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer, lock)
我們需要考慮的是線程安全和非線程安全
- SYNCHRONIZED通過加鎖來確保只有一個線程可以初始化Lazy實例,是線程安全的
- PUBLICATION表示不加鎖,可以并發(fā)訪問多次調(diào)用,但是我之接收第一個返回的值作為Lazy的實例,其他后面返回的是啥玩意兒我不管。這也是線程安全的
- NONE不加鎖,是線程不安全的
總結(jié)
總的來說其實 lateinit 是延遲初始化, by lazy 是懶加載即初始化方式已確定,只是在使用的時候執(zhí)行。
雖然兩者都可以推遲屬性初始化的時間,但是 lateinit var 只是讓編譯期忽略對屬性未初始化的檢查,后續(xù)在哪里以及何時初始化還需要開發(fā)者自己決定。而by lazy真正做到了聲明的同時也指定了延遲初始化時的行為,在屬性被第一次被使用的時候能自動初始化。
并且 lateinit 是可讀寫的,by lazy 是只讀的。
那我們什么時候該使用 lateinit,什么時候使用 by lazy ?
其實大部分情況下都可以通用,只是 by lazy 一般用于非空只讀屬性,需要延遲加載情況,而 lateinit 一般用于非空可變屬性,需要延遲加載情況。
以上就是Kotlin對象的懶加載方式by lazy 與 lateinit 異同詳解的詳細內(nèi)容,更多關(guān)于Kotlin 對象懶加載by lazy lateinit 的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android中實現(xiàn)記事本動態(tài)添加行效果
記事本對我們每個人來說再熟悉不過,下面這篇文章主要給大家介紹了在Android中實現(xiàn)記事本動態(tài)添加行效果的相關(guān)資料,這是最近在開發(fā)中遇到的一個小需求,想著分享出來供大家參考學習,需要的朋友們下面來一起看看吧。2017-06-06Android UI設(shè)計系列之自定義Dialog實現(xiàn)各種風格的對話框效果(7)
這篇文章主要介紹了Android UI設(shè)計系列之自定義Dialog實現(xiàn)各種風格的對話框效果,具有一定的實用性和參考價值,感興趣的小伙伴們可以參考一下2016-06-06Android編程實現(xiàn)二級下拉菜單及快速搜索的方法
這篇文章主要介紹了Android編程實現(xiàn)二級下拉菜單及快速搜索的方法,以實例形式較為詳細的分析了Android實現(xiàn)二級下拉菜單及快速搜索的布局與功能實現(xiàn)技巧,需要的朋友可以參考下2015-11-11android 捕獲系統(tǒng)異常并上傳日志具體實現(xiàn)
這篇文章介紹了android 捕獲系統(tǒng)異常并上傳日志具體實現(xiàn),有需要的朋友可以參考一下2013-09-09