kotlin的 by lazy 和 lateinit 關鍵字的應用

醉過才知酒濃發表於2018-12-01
前言

在kotlin中,一般來說宣告屬性為非空型別必須在建構函式中初始化( 我們知道,kotlin是預設空安全的,任何屬性的宣告都必須初始化,只有宣告支援可空“?”才能把屬性宣告為null)。但是這樣很不方便(例如:屬性可以通過依賴注入來初始化,或者在單元測試的setup方法中初始化)在這種情況下,你不能再建構函式內提供一個非空初始化器。但你仍然想在類體中引用該屬性時避免空檢查。

為了處理這種情況 在kotlin中 我們有兩種解決方法 一種是 lateinit(延遲初始化)修飾符標記,另一種 by lazy(延遲屬性)來實現。

1.lateinit
lateinit修飾符只能用於在類體中的屬性(不是在柱建構函式中宣告的var屬性,並且僅當該屬性沒有自定義getter或setter時),而自Kotlin 1.2起,也可用於頂層屬性和區域性變數。該屬性或變數必須為非空型別,並且不能是原生型別。
class MyTest{
lateinit var test : Test

fun setup{
test = Test()
}

fun test(){
test.method()
}
}
在初始化前訪問lateinit屬性會丟擲一個特定異常,如下:
kotlin.UninitializedPropertyAccessException: lateinit property test has not been initialized
這個異常明確的表示該屬性被訪問及它沒有被初始化的事實;

自kotlin1.2起 給出了一個可以檢測lateinit var 是否初始化的方法:.isInitialized:
使用方法如下:
class Aaa{
lateinit var test:CommonImageLoader

fun isTestInit():Boolean{

return ::test.isInitialized
}
}
返回false 是未初始化 true為已經初始化

但是該方法只能在在 lateinit宣告位於同一個類裡使用 不能再這個類外使用。


在使用lateinit時需要注意:
  1. lateinit 只能作用於var關鍵字標註的屬性
  2. ::test.isInitialized 檢查是否初始化只能在和屬性位於同一個類裡時使用


2.by lazy(延遲屬性)
延遲屬性會在第一次訪問時進行計算,而且這個 只對val 屬性起作用
延遲屬性時接受了一個lambda並返回一個lazy<T>例項的函式,返回的例項可以作為實現延遲屬性的委託:第一次呼叫get()會執行已經傳遞給lazy()的lambda表示式並記錄結果,後續呼叫get()只是返回記錄的結果。
class Aaa{
lateinit var test:Bbb

fun isTestInit():Boolean{

return ::test.isInitialized
}

val test1:Bbb by lazy {
print("bbbbbbbbbbbbb\n")
Bbb("cccccccccccc")
}
}

class Bbb(var bbb:String)

fun main(str:Array<String>){

var aa = Aaa()
while (true) {
if(aa.isTestInit()){
print(aa.test.bbb)
break
}else{
print("................\n")
aa.test = Bbb("aaaaaaa")
}
}
print("\n................\n")
print(aa.test1.bbb)
}

//列印結果
................
aaaaaaa
................
bbbbbbbbbbbbb
cccccccccccc
在lazy的lambda函式裡 最後一行必須是 屬性型別
在預設情況下,對於lazy屬性的值是同步鎖定的(synchronized):該值只在一個執行緒中計算,並且所有的執行緒看到相同的值。如果初始化委託的同步鎖不是必須的,這樣多個執行緒可以同時執行,那麼將LazyThreadSafetyMode.PUBLICATION作為引數傳給lazy()函式。而如果確定初始化將總髮生在單執行緒中,那麼可以將LazyThreadSafetyMode.NONE作為引數傳給lazy()函式,它不會有任何執行緒安全的保證以及相關的開銷。

在預設的情況下 是有同步鎖的 所以lambda函式只執行一次
程式碼如下:
class Aaa{
val test1:ArrayList<String> by lazy{
print("bbbbbbbbbbbbb\n")
ArrayList<String>()
}
}

class Bbb(var bbb:String)

fun main(str:Array<String>){

var aa = Aaa()

Thread(){
Thread.sleep(2)
print("\n.........1.......\n")
aa.test1
aa.test1.add("aaa")
}.start()
Thread(){
Thread.sleep(1)
print("\n.........2.......\n")
aa.test1
aa.test1.add("bbb")
}.start()
Thread(){
print("\n.........3.......\n")
aa.test1
aa.test1.add("ccc")
}.start()

Thread.sleep(300)
print("\n test1列表個數:"+aa.test1.size)
}
//列印日誌
.........3.......

.........1.......

.........2.......
bbbbbbbbbbbbb

test1列表個數:3

/**
* Initializer function can be called several times on concurrent access to uninitialized [Lazy] instance value,
* but only the first returned value will be used as the value of [Lazy] instance.
初始化器函式可以在併發訪問未初始化的[延遲]例項值時多次呼叫,
但是隻有第一個返回值將用作[Lazy]例項的值。

*/
LazyThreadSafetyMode.PUBLICATION :在這個模式下 如果多執行緒同時訪問,在訪問的時候都沒有建立這個例項 lambda函式會多次呼叫 但是建立的例項只有第一次返回的例項是有效的
如下程式碼:
class Aaa{
val test1:ArrayList<String> by lazy(LazyThreadSafetyMode.PUBLICATION){
print("bbbbbbbbbbbbb\n")
ArrayList<String>()
}
}

class Bbb(var bbb:String)

fun main(str:Array<String>){

var aa = Aaa()

Thread(){
Thread.sleep(2)
print("\n.........1.......\n")
aa.test1
aa.test1.add("aaa")
}.start()
Thread(){
Thread.sleep(1)
print("\n.........2.......\n")
aa.test1
aa.test1.add("bbb")
}.start()
Thread(){
print("\n.........3.......\n")
aa.test1
aa.test1.add("ccc")
}.start()

Thread.sleep(300)
print("\n test1列表個數:"+aa.test1.size)
}
//列印日誌
.........2.......

.........1.......
bbbbbbbbbbbbb
bbbbbbbbbbbbb

.........3.......

test1列表個數:3

/**
* No locks are used to synchronize an access to the [Lazy] instance value; if the instance is accessed from multiple threads, its behavior is undefined.
*
* This mode should not be used unless the [Lazy] instance is guaranteed never to be initialized from more than one thread.
不使用鎖來同步對[Lazy]例項值的訪問;如果從多個執行緒訪問該例項,則其行為未定義。
除非保證[Lazy]例項永遠不會從多個執行緒初始化,否則不應該使用這種模式。
*/
LazyThreadSafetyMode.NONE :在這個模式下 如果多個執行緒同時訪問 在訪問的時候都沒有建立這個例項的話 會建立多個例項並返回 並且後返回的會覆蓋前面返回的例項
如下程式碼:

class Aaa{

val test1:ArrayList<String> by lazy(LazyThreadSafetyMode.NONE){
print("bbbbbbbbbbbbb\n")
ArrayList<String>()
}
}

class Bbb(var bbb:String)

fun main(str:Array<String>){

var aa = Aaa()

Thread(){
Thread.sleep(2)
print("\n.........1.......\n")
aa.test1
aa.test1.add("aaa")
}.start()
Thread(){
Thread.sleep(1)
print("\n.........2.......\n")
aa.test1
aa.test1.add("bbb")
}.start()
Thread(){
print("\n.........3.......\n")
aa.test1
aa.test1.add("ccc")
}.start()

Thread.sleep(300)
print("\n test1列表個數:"+aa.test1.size)
}
//列印的日誌
.........3.......
.........2.......
.........1.......
bbbbbbbbbbbbb
bbbbbbbbbbbbb
bbbbbbbbbbbbb
test1列表個數:1

使用by lazy需要注意:
  1. by lazy 只能作用於val關鍵字標註的屬性
  2. 只有在屬性被第一次呼叫才會執行“lazy{}”裡面的內容,第二次在呼叫時,不會在執行“lazy()”裡面的內容
  3. 使用不同的模式時需要注意執行緒同步問題。


相關文章