<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
使用 Kotlin 進行開發,對於 latelinit 和 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 範例),並再次檢查是否已經初始化:
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
ExceptionisInitialized
在使用前進行檢查lazy 用作變數的延遲初始化:
initializer
函數體lock
物件和其他實現 mode
到此這篇關於聊聊Kotlin 中 lateinit 和 lazy 的區別解析的文章就介紹到這了,更多相關Kotlin 中 lateinit 和 lazy區別內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45