您好,登錄后才能下訂單哦!
這篇“Kotlin中lateinit和lazy的原理區別是什么”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Kotlin中lateinit和lazy的原理區別是什么”文章吧。
非空類型可以使用 lateinit 關鍵字達到延遲初始化。
class InitTest() { lateinit var name: String public fun checkName(): Boolean = name.isNotEmpty() }
如果在使用前沒有初始化的話會發生如下 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 則拋出對應的 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() }
在反編譯之后發現變量多了 @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
的話進行反編譯之后可以發現就是在使用前進行了 null 檢查,為空直接執行預設邏輯,反之才進行變量的使用。
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 的命名和 lateinit 類似,但使用場景不同。其是用于懶加載,即初始化方式已確定,只是在使用的時候執行。而且修飾的只是能是 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 創建實例的時候,實際分配給 lazy 變量的是 Lazy 接口類型,并非 T 類型,變量會在 Lazy 中以 value 暫存,當使用該變量的時候會獲取 Lazy 的 value 屬性。
Lazy 接口的默認 mode 是 LazyThreadSafetyMode.SYNCHRONIZED
,其默認實現是 SynchronizedLazyImpl,該實現中 _value 屬性為實際的值,用 volatile 修飾。
value 則通過 get() 從 _value 中讀寫,get() 將先檢查 _value 是否尚未初始化
已經初始化過的話,轉換為 T 類型后返回
反之,執行同步方法(默認情況下 lock 對象為 impl 實例),并再次檢查是否已經初始化:
已經初始化過的話,轉換為 T 類型后返回
反之,執行用于初始化的函數 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 下還可以指定內部同步的 lock 對象。
val name by lazy(lock) { "test" }
lazy 還可以指定其他 mode,比如 PUBLICATION
,內部采用不同于 synchronized
的 CAS
機制。
val name by lazy(LazyThreadSafetyMode.PUBLICATION) { "test" }
lazy 還可以指定 NONE
mode,線程不安全。
val name by lazy(LazyThreadSafetyMode.NONE) { "test" }
lateinit 和 lazy 都是用于初始化場景,用法和原理有些區別,做個簡單總結:
lateinit 用作非空類型的初始化:
在使用前需要初始化
如果使用時沒有初始化內部會拋出 UninitializedPropertyAccess
Exception
可配合 isInitialized
在使用前進行檢查
lazy 用作變量的延遲初始化:
定義的時候已經明確了 initializer
函數體
使用的時候才進行初始化,內部默認通過同步鎖和雙重校驗的方式返回持有的實例
還支持設置 lock
對象和其他實現 mode
以上就是關于“Kotlin中lateinit和lazy的原理區別是什么”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。