聊聊Kotlin?中?lateinit?和?lazy?的原理區(qū)別
使用 Kotlin 進行開發(fā),對于 latelinit 和 lazy 肯定不陌生。但其原理上的區(qū)別,可能鮮少了解過,借著本篇文章普及下這方面的知識。
lateinit
用法
非空類型可以使用 lateinit 關(guān)鍵字達到延遲初始化。
class InitTest() { lateinit var name: String ? public fun checkName(): Boolean = name.isNotEmpty() }
如果在使用前沒有初始化的話會發(fā)生如下 Exception。
AndroidRuntime: FATAL EXCEPTION: main Caused by: kotlin.UninitializedPropertyAccessException: lateinit property name has not been initialized at com.example.tiramisu_demo.kotlin.InitTest.getName(InitTest.kt:4) at com.example.tiramisu_demo.kotlin.InitTest.checkName(InitTest.kt:10) at com.example.tiramisu_demo.MainActivity.testInit(MainActivity.kt:365) at com.example.tiramisu_demo.MainActivity.onButtonClick(MainActivity.kt:371) ...
為防止上述的 Exception,可以在使用前通過 ::xxx.isInitialized
進行判斷。
class InitTest() { lateinit var name: String ? fun checkName(): Boolean { return if (::name.isInitialized) { name.isNotEmpty() } else { false } } }
Init: testInit():false
當 name 初始化過之后使用亦可正常。
class InitTest() { lateinit var name: String ? fun injectName(name: String) { this.name = name } ? fun checkName(): Boolean { return if (::name.isInitialized) { name.isNotEmpty() } else { false } } }
Init: testInit():true
原理
反編譯之后可以看到該變量沒有 @NotNull 注解,使用的時候要 check 是否為 null。
public final class InitTest { public String name; @NotNull public final String getName() { String var10000 = this.name; if (var10000 == null) { Intrinsics.throwUninitializedPropertyAccessException("name"); } ? return var10000; } ? public final boolean checkName() { String var10000 = this.name; if (var10000 == null) { Intrinsics.throwUninitializedPropertyAccessException("name"); } ? CharSequence var1 = (CharSequence)var10000; return var1.length() > 0; } }
null 則拋出對應(yīng)的 UninitializedPropertyAccessException。
public class Intrinsics { public static void throwUninitializedPropertyAccessException(String propertyName) { throwUninitializedProperty("lateinit property " + propertyName + " has not been initialized"); } ? public static void throwUninitializedProperty(String message) { throw sanitizeStackTrace(new UninitializedPropertyAccessException(message)); } ? private static <T extends Throwable> T sanitizeStackTrace(T throwable) { return sanitizeStackTrace(throwable, Intrinsics.class.getName()); } ? static <T extends Throwable> T sanitizeStackTrace(T throwable, String classNameToDrop) { StackTraceElement[] stackTrace = throwable.getStackTrace(); int size = stackTrace.length; ? int lastIntrinsic = -1; for (int i = 0; i < size; i++) { if (classNameToDrop.equals(stackTrace[i].getClassName())) { lastIntrinsic = i; } } ? StackTraceElement[] newStackTrace = Arrays.copyOfRange(stackTrace, lastIntrinsic + 1, size); throwable.setStackTrace(newStackTrace); return throwable; } } ? public actual class UninitializedPropertyAccessException : RuntimeException { ... }
如果是變量是不加 lateinit 的非空類型,定義的時候即需要初始化。
class InitTest() { val name: String = "test" ? public fun checkName(): Boolean = name.isNotEmpty() }
在反編譯之后發(fā)現(xiàn)變量多了 @NotNull 注解,可直接使用。
public final class InitTest { @NotNull private String name = "test"; ? @NotNull public final String getName() { return this.name; } ? public final boolean checkName() { CharSequence var1 = (CharSequence)this.name; return var1.length() > 0; } }
::xxx.isInitialized
的話進行反編譯之后可以發(fā)現(xiàn)就是在使用前進行了 null 檢查,為空直接執(zhí)行預(yù)設(shè)邏輯,反之才進行變量的使用。
public final class InitTest { public String name; ... public final boolean checkName() { boolean var2; if (((InitTest)this).name != null) { String var10000 = this.name; if (var10000 == null) { Intrinsics.throwUninitializedPropertyAccessException("name"); } ? CharSequence var1 = (CharSequence)var10000; var2 = var1.length() > 0; } else { var2 = false; } ? return var2; } }
lazy
用法
lazy 的命名和 lateinit 類似,但使用場景不同。其是用于懶加載,即初始化方式已確定,只是在使用的時候執(zhí)行。而且修飾的只是能是 val 常量。
class InitTest { val name by lazy { "test" } public fun checkName(): Boolean = name.isNotEmpty() }
lazy 修飾的變量可以直接使用,不用擔心 NPE。
Init: testInit():true
原理
上述是 lazy 最常見的用法,反編譯之后的代碼如下:
public final class InitTest { @NotNull private final Lazy name$delegate; ? @NotNull public final String getName() { Lazy var1 = this.name$delegate; return (String)var1.getValue(); } ? public final boolean checkName() { CharSequence var1 = (CharSequence)this.getName(); return var1.length() > 0; } ? public InitTest() { this.name$delegate = LazyKt.lazy((Function0)null.INSTANCE); } }
所屬 class 創(chuàng)建實例的時候,實際分配給 lazy 變量的是 Lazy 接口類型,并非 T 類型,變量會在 Lazy 中以 value 暫存,當使用該變量的時候會獲取 Lazy 的 value 屬性。
Lazy 接口的默認 mode 是 LazyThreadSafetyMode.SYNCHRONIZED
,其默認實現(xiàn)是 SynchronizedLazyImpl,該實現(xiàn)中 _value 屬性為實際的值,用 volatile 修飾。
value 則通過 get() 從 _value 中讀寫,get() 將先檢查 _value 是否尚未初始化
已經(jīng)初始化過的話,轉(zhuǎn)換為 T 類型后返回
反之,執(zhí)行同步方法(默認情況下 lock 對象為 impl 實例),并再次檢查是否已經(jīng)初始化:
- 已經(jīng)初始化過的話,轉(zhuǎn)換為 T 類型后返回
- 反之,執(zhí)行用于初始化的函數(shù) initializer,其返回值存放在 _value 中,并返回
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer) ? 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 } } } ? override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE ? override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet." ? private fun writeReplace(): Any = InitializedLazyImpl(value) }
總之跟 Java 里雙重檢查懶漢模式獲取單例的寫法非常類似。
public class Singleton { private static volatile Singleton singleton; ? private Singleton() { } ? public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
lazy 在上述默認的 SYNCHRONIZED mode 下還可以指定內(nèi)部同步的 lock 對象。
val name by lazy(lock) { "test" }
lazy 還可以指定其他 mode,比如 PUBLICATION
,內(nèi)部采用不同于 synchronized
的 CAS
機制。
val name by lazy(LazyThreadSafetyMode.PUBLICATION) { "test" }
lazy 還可以指定 NONE
mode,線程不安全。
val name by lazy(LazyThreadSafetyMode.NONE) { "test" }
the end
lateinit 和 lazy 都是用于初始化場景,用法和原理有些區(qū)別,做個簡單總結(jié):
lateinit 用作非空類型的初始化:
- 在使用前需要初始化
- 如果使用時沒有初始化內(nèi)部會拋出
UninitializedPropertyAccess
Exception - 可配合
isInitialized
在使用前進行檢查
lazy 用作變量的延遲初始化:
- 定義的時候已經(jīng)明確了
initializer
函數(shù)體 - 使用的時候才進行初始化,內(nèi)部默認通過同步鎖和雙重校驗的方式返回持有的實例
- 還支持設(shè)置
lock
對象和其他實現(xiàn)mode
references
到此這篇關(guān)于聊聊Kotlin 中 lateinit 和 lazy 的區(qū)別解析的文章就介紹到這了,更多相關(guān)Kotlin 中 lateinit 和 lazy區(qū)別內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
利用數(shù)組實現(xiàn)棧(Java實現(xiàn))
這篇文章主要為大家詳細介紹了利用數(shù)組實現(xiàn)棧,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-09-09Spring?AOP操作的相關(guān)術(shù)語及環(huán)境準備
這篇文章主要為大家介紹了Spring?AOP操作的相關(guān)術(shù)語及環(huán)境準備學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-05-05