聊聊Kotlin?中?lateinit?和?lazy?的原理區(qū)別
使用 Kotlin 進(jìn)行開發(fā),對(duì)于 latelinit 和 lazy 肯定不陌生。但其原理上的區(qū)別,可能鮮少了解過,借著本篇文章普及下這方面的知識(shí)。
lateinit
用法
非空類型可以使用 lateinit 關(guān)鍵字達(dá)到延遲初始化。
class InitTest() {
lateinit var name: String
?
public fun checkName(): Boolean = name.isNotEmpty()
}如果在使用前沒有初始化的話會(huì)發(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 進(jìn)行判斷。
class InitTest() {
lateinit var name: String
?
fun checkName(): Boolean {
return if (::name.isInitialized) {
name.isNotEmpty()
} else {
false
}
}
}Init: testInit():false
當(dāng) 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 注解,使用的時(shí)候要 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 則拋出對(duì)應(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 的非空類型,定義的時(shí)候即需要初始化。
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 的話進(jìn)行反編譯之后可以發(fā)現(xiàn)就是在使用前進(jìn)行了 null 檢查,為空直接執(zhí)行預(yù)設(shè)邏輯,反之才進(jìn)行變量的使用。
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 類似,但使用場景不同。其是用于懶加載,即初始化方式已確定,只是在使用的時(shí)候執(zhí)行。而且修飾的只是能是 val 常量。
class InitTest {
val name by lazy {
"test"
}
public fun checkName(): Boolean = name.isNotEmpty()
}lazy 修飾的變量可以直接使用,不用擔(dān)心 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)建實(shí)例的時(shí)候,實(shí)際分配給 lazy 變量的是 Lazy 接口類型,并非 T 類型,變量會(huì)在 Lazy 中以 value 暫存,當(dāng)使用該變量的時(shí)候會(huì)獲取 Lazy 的 value 屬性。
Lazy 接口的默認(rèn) mode 是 LazyThreadSafetyMode.SYNCHRONIZED,其默認(rèn)實(shí)現(xiàn)是 SynchronizedLazyImpl,該實(shí)現(xiàn)中 _value 屬性為實(shí)際的值,用 volatile 修飾。
value 則通過 get() 從 _value 中讀寫,get() 將先檢查 _value 是否尚未初始化
已經(jīng)初始化過的話,轉(zhuǎn)換為 T 類型后返回
反之,執(zhí)行同步方法(默認(rèn)情況下 lock 對(duì)象為 impl 實(shí)例),并再次檢查是否已經(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 在上述默認(rèn)的 SYNCHRONIZED mode 下還可以指定內(nèi)部同步的 lock 對(duì)象。
val name by lazy(lock) {
"test"
}lazy 還可以指定其他 mode,比如 PUBLICATION,內(nèi)部采用不同于 synchronized 的 CAS 機(jī)制。
val name by lazy(LazyThreadSafetyMode.PUBLICATION) {
"test"
}lazy 還可以指定 NONE mode,線程不安全。
val name by lazy(LazyThreadSafetyMode.NONE) {
"test"
}the end
lateinit 和 lazy 都是用于初始化場景,用法和原理有些區(qū)別,做個(gè)簡單總結(jié):
lateinit 用作非空類型的初始化:
- 在使用前需要初始化
- 如果使用時(shí)沒有初始化內(nèi)部會(huì)拋出
UninitializedPropertyAccessException - 可配合
isInitialized在使用前進(jìn)行檢查
lazy 用作變量的延遲初始化:
- 定義的時(shí)候已經(jīng)明確了
initializer函數(shù)體 - 使用的時(shí)候才進(jìn)行初始化,內(nèi)部默認(rèn)通過同步鎖和雙重校驗(yàn)的方式返回持有的實(shí)例
- 還支持設(shè)置
lock對(duì)象和其他實(shí)現(xiàn)mode
references
到此這篇關(guān)于聊聊Kotlin 中 lateinit 和 lazy 的區(qū)別解析的文章就介紹到這了,更多相關(guān)Kotlin 中 lateinit 和 lazy區(qū)別內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring MVC 中 AJAX請(qǐng)求并返回JSON的示例
本篇文章主要介紹了Spring MVC 中 AJAX請(qǐng)求并返回JSON,具有一定的參考價(jià)值,有興趣的可以了解一下。2017-01-01
利用數(shù)組實(shí)現(xiàn)棧(Java實(shí)現(xiàn))
這篇文章主要為大家詳細(xì)介紹了利用數(shù)組實(shí)現(xiàn)棧,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-09-09
IDEA 2020.2 部署JSF項(xiàng)目的詳細(xì)過程
本文通過圖文并茂的形式教大家如何在IDEA中創(chuàng)建一個(gè)JSF項(xiàng)目及遇到問題的解決方法,感興趣的朋友跟隨小編一起看看吧2021-09-09
Spring?AOP操作的相關(guān)術(shù)語及環(huán)境準(zhǔn)備
這篇文章主要為大家介紹了Spring?AOP操作的相關(guān)術(shù)語及環(huán)境準(zhǔn)備學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05
java模擬post請(qǐng)求發(fā)送json的例子
本篇文章主要介紹了java模擬post請(qǐng)求發(fā)送json的例子,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08
使用maven插件對(duì)java工程進(jìn)行打包過程解析
這篇文章主要介紹了使用maven插件對(duì)java工程進(jìn)行打包過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08

