Koltin中屬性在聲明的同時也要求要被初始化,不然會報錯。 例如如下代碼:java
private var name0: String //報錯 private var name1: String = "xiaoming" //不報錯 private var name2: String? = null //不報錯 複製代碼
但是有的時候,我並不想聲明一個類型可空的對象,並且我也沒辦法在對象一聲明的時候就爲它初始化,那麼這時就須要用到Kotlin提供的延遲初始化。
Kotlin中有兩種延遲初始化的方式。一種是lateinit var,一種是by lazy。安全
private lateinit var name: String 複製代碼
lateinit var只能用來修飾類屬性,不能用來修飾局部變量,而且只能用來修飾對象,不能用來修飾基本類型(由於基本類型的屬性在類加載後的準備階段都會被初始化爲默認值)。
lateinit var的做用也比較簡單,就是讓編譯期在檢查時不要由於屬性變量未被初始化而報錯。
Kotlin相信當開發者顯式使用lateinit var 關鍵字的時候,他必定也會在後面某個合理的時機將該屬性對象初始化的(然而,誰知道呢,也許他用完纔想起還沒初始化)。bash
by lazy自己是一種屬性委託。屬性委託的關鍵字是by
。by lazy 的寫法以下:markdown
//用於屬性延遲初始化 val name: Int by lazy { 1 } //用於局部變量延遲初始化 public fun foo() { val bar by lazy { "hello" } println(bar) } 複製代碼
如下以name屬性爲表明來說解by kazy的原理,局部變量的初始化也是同樣的原理。
by lazy要求屬性聲明爲val
,即不可變變量,在java中至關於被final
修飾。
這意味着該變量一旦初始化後就不容許再被修改值了(基本類型是值不能被修改,對象類型是引用不能被修改)。{}
內的操做就是返回惟一一次初始化的結果。
by lazy可使用於類屬性或者局部變量。jvm
寫一段最簡單的代碼分析by lazy的實現:編輯器
class TestCase { private val name: Int by lazy { 1 } fun printname() { println(name) } } 複製代碼
在IDEA中點擊toolbar中的 Tools -> Kotlin -> Show Kotlin ByteCode, 查看編輯器右側的工具欄:
ide
更完整的字節碼片斷以下:函數
public <init>()V L0 LINENUMBER 3 L0 ALOAD 0 INVOKESPECIAL java/lang/Object.<init> ()V L1 LINENUMBER 5 L1 ALOAD 0 GETSTATIC com/rhythm7/bylazy/TestCase$name$2.INSTANCE : Lcom/rhythm7/bylazy/TestCase$name$2; CHECKCAST kotlin/jvm/functions/Function0 INVOKESTATIC kotlin/LazyKt.lazy (Lkotlin/jvm/functions/Function0;)Lkotlin/Lazy; PUTname com/rhythm7/bylazy/TestCase.name$delegate : Lkotlin/Lazy; RETURN L2 LOCALVARIABLE this Lcom/rhythm7/bylazy/TestCase; L0 L2 0 MAXSTACK = 2 MAXLOCALS = 1 複製代碼
該段代碼是在字節碼生成的public <clinit>()V
方法內的。之因此是在該方法內,是由於非單例object的Kotlin類的屬性初始化代碼語句通過編譯器處理後都會被收集到該方法內,若是是object對象,對應的屬性初始化代碼語句則會被收集到static <clinit>()V
方法中。另外,在字節碼中,這兩個方法是擁有不一樣方法簽名的,這與語言級別上判斷兩個方法是否相同的方式有所不一樣。前者是實例構造方法,後者是類構造方法。
L0與L1之間的字節碼錶明調用了Object()的構造方法,這是默認的父類構造方法。L2以後的是本地變量表說明。L1與L2之間的字節碼對應以下kotlin代碼:工具
private val name: Int by lazy { 1 } 複製代碼
L1與L2之間這段字節碼的意思是:
源代碼行號5對應字節碼方法體內的行號1; 將this(非靜態方法默認的第一個本地變量)推送至棧頂;
獲取靜態變量com.rhythm7.bylazy.TestCase$name$2.INSTANCE
;
檢驗INSTANCE可否轉換爲kotlin.jvm.functions.Function0
類;
調用靜態方法kotlin.LazyKt.lazy(kotlin.jvm.functions.Function0)
,將INSTANCE做爲參數傳入,並得到一個kotlin.Lazy
類型的返回值;
將以上返回值賦值給com.rhythm7.bylazy.TestCase.name$delegate
;
最後結束方法。ui
至關於java代碼:
TestCase() { name$delegate = LazyKt.lazy((Function0)name$2.INSTANCE) } 複製代碼
其中name$delegate
是編譯後生成的屬性,對象類型爲Lazy。
private final Lkotlin/Lazy; name$delegate 複製代碼
name$2
都是編譯後生成的內部類。
final class com/rhythm7/bylazy/TestCase$name$2 extends kotlin/jvm/internal/Lambda implements kotlin/jvm/functions/Function0 複製代碼
name$2
繼承了kotlin.jvm.internal.Lambda類並實現了kotlin.jvm.functions.Function0接口, 能夠看出name$2
其實就是kotlin函數參數類型()->T
的具體實現,經過字節碼分析不難知道name$2.INSTANCE則是該實現類的一個靜態對象實例。
因此以上字節碼又至關於Koltin中的:
init {
name$delegate = lazy(()->{})
}
複製代碼
然而,這些代碼的做用僅僅是給一個編譯期生成的屬性變量賦值而已,並無其餘的操做。
真正實現屬性變量延遲初始化的地方實際上是在屬性name的getter方法裏。
若是在java代碼中調用過kotlin代碼,會發現java代碼中只能經過setter或getter的方式訪問koltin編寫的對象屬性,這是由於kotlin中默認會對屬性添加private
修飾符,並根據該屬性變量是val
仍是var
生成getter或getter和setter一塊兒生成。而後又根據對該屬性的訪問權限給getter和setter添加對應的訪問權限修飾符(默認是public)。
查看getName()的具體實現:
private final getName()I L0 ALOAD 0 GETFIELD com/rhythm7/bylazy/TestCase.name$delegate : Lkotlin/Lazy; ASTORE 1 ALOAD 0 ASTORE 2 GETSTATIC com/rhythm7/bylazy/TestCase.$$delegatedProperties : [Lkotlin/reflect/KProperty; ICONST_0 AALOAD ASTORE 3 L1 ALOAD 1 INVOKEINTERFACE kotlin/Lazy.getValue ()Ljava/lang/Object; L2 CHECKCAST java/lang/Number INVOKEVIRTUAL java/lang/Number.intValue ()I IRETURN L3 LOCALVARIABLE this Lcom/rhythm7/bylazy/TestCase; L0 L3 0 MAXSTACK = 2 MAXLOCALS = 4 複製代碼
至關於java代碼:
private final int getName(){ Lazy var1 = this.name$delegate; KProperty var2 = this.?delegatedProperties[0] return ((Number)var1.getValue()).intValue() } 複製代碼
能夠看到name的getter方法實際上是返回了 name$delegate.getValue()
方法。?delegatedProperties
是編譯後自動生成的屬性,但在此處並無用到,因此不用關心。
那麼如今咱們要關心的就只有name$delegate.getValue()
,也就是Lazy類getValue()
方法的具體實現了。
先看LazyKt.lazy(()->T)的實現:
public fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer) 複製代碼
再看SynchronizedLazyImpl
類的具體實現:
private 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 } } } ...... } 複製代碼
以上代碼的閱讀難度就很是低了。
SynchronizedLazyImpl繼承了Lazy類,並指定了泛型類型,而後重寫了Lazy父類的getValue()方法。 getValue()方法中會對_value
是否已初始化作判斷,並返回_value
,從而實現value的延遲初始化的做用。
注意,對value的初始化行爲自己是線程安全的。
總結一下,當一個屬性name須要by lazy時,具體是怎麼實現的:
那麼,再總結一下,lateinit var和by lazy哪一個更好用? 首先二者的應用場景是略有不一樣的。 而後,雖然二者均可以推遲屬性初始化的時間,可是lateinit var只是讓編譯期忽略對屬性未初始化的檢查,後續在哪裏以及什麼時候初始化還須要開發者本身決定。 而by lazy真正作到了聲明的同時也指定了延遲初始化時的行爲,在屬性被第一次被使用的時候能自動初始化。但這些功能是要爲此付出一丟丟代價的。