Kotlin lateinit 和 by lazy實現(xiàn)原理

lateinit

lateinit:用來修飾var類型成員變量乍桂,用來表示該變量可以在晚些時候初始化,用來避免不必要的空檢查。自Kotlin1.2版本開始,lateinit也可以用來修飾頂層變量局部變量榕堰。

//頂層變量
lateinit var topVariable: String

class MyTest {
    //成員變量
    lateinit var name: String

    fun myfun() {
        //局部變量
        lateinit var address: String

    }
}

注意:

  1. lateinit不能修飾原始數(shù)據(jù)類型,原始類型的數(shù)據(jù)應該使用默認的數(shù)據(jù)來進行初始化屈留,比如Int類型的變量可以使用0進行初始化局冰。
  2. lateinit修飾的var類型變量不能有自定義的gettersetter

如果我們訪問一個 lateinit修飾的var類型變量灌危,但是該變量沒有被初始化,那么就會拋出異常碳胳。

kotlin.UninitializedPropertyAccessException: lateinit property name has not been initialized

Kotlin1.2版本開始勇蝙,我們可以檢查一個lateinit var類型變量的變量是否被初始化了。

lateinit var topVariable: String

fun main() {

    val myTest = MyTest()
    myTest.check()
    //為什么不能在這里檢車MyTest的name屬性是否被初始化了呢挨约?還不清楚
    //println(myTest::name.isInitialized)
    println(::topVariable.isInitialized)
}

class MyTest {

    lateinit var name: String

    fun check() {
        if (this::name.isInitialized) {
            println(name)
        } else {
            println("name 還沒有初始化")
        }
    }
}

lateinit的實現(xiàn)原理:我感覺沒什么原理吧味混,應該就是告訴編譯器不要去檢查變量是否為空。然后我們在引用變量的時候就判斷一下诫惭,如果變量為空翁锡,也就是沒有初始化,那么就拋出異常夕土,否者就正常返回馆衔。

查看反編譯的Kotlin字節(jié)碼瘟判,只看生成的getName方法

//注釋1處
@NotNull
public final String getName() {
      String var10000 = this.name;
      //注釋2處,檢查
      if (var10000 == null) {
         Intrinsics.throwUninitializedPropertyAccessException("name");
      }

      return var10000;
}

注釋1處角溃,標記這個方法返回不為空拷获。
注釋2處,檢查减细,如果變量為空了匆瓜,拋出異常。

總結(jié):lateinit可以用來修飾var類型變量來避免不必要的檢查未蝌,自Kotlin1.2版本開始驮吱,我們可以檢查一個lateinit var類型變量的變量是否被初始化了。

lazy

lazy可以用來實現(xiàn)val類型變量的延遲初始化萧吠】饭荩可以修飾成員變量,修飾頂層變量和局部變量怎憋。

//修飾頂層變量
val topInt: Int by lazy {
    1
}

fun main() {
    val byLazyTest = ByLazyTest()
    byLazyTest.printName()
}

class ByLazyTest {
    //修飾成員變量
    private val name: String by lazy {
        "dmw"
    }

    fun printName() {
        //修飾局部變量
        val address: String by lazy {
            "dmw"
        }

        print(this.name)
    }
}

我們以一個最簡單的例子來分析又碌,如下所示:

class LazyInitDemo {

     val myName: String by lazy(getNameFromPreference())

    private fun getNameFromPreference(): () -> String = { "John" }

}

其實我們也可以簡單的寫成下面這樣。

class LazyInitDemo {
    val myName: String by lazy{"John"}
}

我們先來看看 lazy 這個方法的簽名

方法定義在LazyJVM.kt文件中

/**
 * 使用指定的初始化函數(shù)[initializer]來創(chuàng)建一個[Lazy] 類型的對象绊袋。
 * 默認的線程模型是[LazyThreadSafetyMode.SYNCHRONIZED].
 */
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = 
            SynchronizedLazyImpl(initializer)

注釋中已經(jīng)很清晰了:使用指定的初始化函數(shù)[initializer]來創(chuàng)建一個[Lazy] 類型的對象毕匀。默認的線程模型是[LazyThreadSafetyMode.SYNCHRONIZED]。返回的是一個SynchronizedLazyImpl類型的對象癌别。線程模型是什么東西皂岔?就是為了處理傳入的初始化函數(shù)[initializer]的調(diào)用,在單線程或者多線程下的調(diào)用展姐。lazy方法還有一個重載函數(shù)躁垛,我們可以顯式的傳入線程模型。

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)
}

這里我們就不討論線程模型的問題了圾笨,大家可以看一看教馆,很簡單。

通過上面的敘述我們可以知道擂达,by lazy指定的函數(shù)就是一個lambda表達式{ "John" }土铺,是一個() -> T類型的初始化函數(shù)。

查看反編譯的Kotlin字節(jié)碼

public final class LazyInitDemo {

   //注釋1處
   @NotNull
   private final Lazy myName$delegate = LazyKt.lazy(this.getNameFromPreference());
   
   @NotNull
   public final String getMyName() {
      Lazy var1 = this.myName$delegate;
      //注釋2處
      return (String)var1.getValue();
   }

   private final Function0 getNameFromPreference() {
      return (Function0)null.INSTANCE;
   }
}

注釋1處板鬓,生成了一個Lazy類型的變量myName$delegate悲敷,就是我們的屬性名加上后綴$delegate。我們知道默認情況下返回的是一個SynchronizedLazyImpl對象俭令。

注釋2處后德,調(diào)用SynchronizedLazyImpl的getValue方法。我們來看一看:

SynchronizedLazyImpl類在LazyJVM.kt文件中

//LazyJVM.kt
internal object UNINITIALIZED_VALUE

UNINITIALIZED_VALUE用來表示value是否初始化了

//LazyJVM.kt
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
    //重寫了value屬性
    override val value: T
        get() {
            val _v1 = _value
            if (_v1 !== UNINITIALIZED_VALUE) {
                //注釋1處抄腔,已經(jīng)初始化了瓢湃,直接返回
                return _v1 as T
            }
            //使用同步方法理张,避免多次初始化
            return synchronized(lock) {
                val _v2 = _value
                if (_v2 !== UNINITIALIZED_VALUE) {
                    //注釋2處,別的線程已經(jīng)初始化了箱季,直接返回
                    (_v2 as T)
                } else {
                    //注釋3處涯穷,調(diào)用傳入的lambda進行初始化并返回
                    val typedValue = initializer!!()
                    _value = typedValue
                    initializer = null
                    typedValue
                }
            }
        }
    //是否已經(jīng)初始化了
    override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
    //...
}

注釋1處,如果 _value!= UNINITIALIZED_VALUE藏雏,說明已經(jīng)調(diào)用傳入的initializer初始化過了拷况,直接返回_value
注釋2處掘殴,如果別的線程已經(jīng)初始化了赚瘦,直接返回_value
注釋3處奏寨,當前還沒有初始化就調(diào)用initializer進行初始化起意,并將初始化值賦值給_value,然后返回病瞳。

以后每次調(diào)用getValue方法都是直接將_value的值返回揽咕,從而實現(xiàn)屬性值的唯一一次初始化。

其實到這里lazy的實現(xiàn)原理已經(jīng)很清楚了套菜,我們再結(jié)合圖片總結(jié)一下:


step1.png
  1. 將初始化lambda表達式存儲在initializer中
step2.png
  1. SynchronizedLazyImpl重寫了Lazy的value的getter方法亲善, 調(diào)用getValue方法返回_value_value的默認值是UNINITIALIZED_VALUE逗柴,表示還沒有進行初始化蛹头。
step3.png
  1. 如果調(diào)用getValue方法的時候,_value值是UNINITIALIZED_VALUE戏溺,那么就調(diào)用initializer進行初始化渣蜗,并將初始化好的值賦值給_value
step4.png
  1. 以后每次調(diào)用getValue方法都是直接將_value的值返回旷祸。

參考鏈接:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末耕拷,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子肋僧,更是在濱河造成了極大的恐慌斑胜,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嫌吠,死亡現(xiàn)場離奇詭異,居然都是意外死亡掺炭,警方通過查閱死者的電腦和手機辫诅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來涧狮,“玉大人炕矮,你說我怎么就攤上這事么夫。” “怎么了肤视?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵档痪,是天一觀的道長。 經(jīng)常有香客問我邢滑,道長腐螟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任困后,我火速辦了婚禮乐纸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘摇予。我一直安慰自己汽绢,他們只是感情好,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布侧戴。 她就那樣靜靜地躺著宁昭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪酗宋。 梳的紋絲不亂的頭發(fā)上积仗,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天,我揣著相機與錄音本缠,去河邊找鬼斥扛。 笑死,一個胖子當著我的面吹牛丹锹,可吹牛的內(nèi)容都是我干的稀颁。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼楣黍,長吁一口氣:“原來是場噩夢啊……” “哼匾灶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起租漂,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤阶女,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后哩治,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體秃踩,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年业筏,在試婚紗的時候發(fā)現(xiàn)自己被綠了憔杨。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡蒜胖,死狀恐怖消别,靈堂內(nèi)的尸體忽然破棺而出抛蚤,到底是詐尸還是另有隱情,我是刑警寧澤寻狂,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布岁经,位于F島的核電站,受9級特大地震影響蛇券,放射性物質(zhì)發(fā)生泄漏缀壤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一怀读、第九天 我趴在偏房一處隱蔽的房頂上張望诉位。 院中可真熱鬧,春花似錦菜枷、人聲如沸苍糠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽岳瞭。三九已至,卻和暖如春蚊锹,著一層夾襖步出監(jiān)牢的瞬間瞳筏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工牡昆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留姚炕,地道東北人。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓丢烘,卻偏偏與公主長得像柱宦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子播瞳,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容