聊聊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)部會拋出
UninitializedPropertyAccessException - 可配合
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)文章
Java并發(fā)包線程池ThreadPoolExecutor的實現(xiàn)
本文主要介紹了Java并發(fā)包線程池ThreadPoolExecutor的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習或者工作具有一定的參考學(xué)習價值,需要的朋友們下面隨著小編來一起學(xué)習學(xué)習吧2022-04-04
JDK1.7 之java.nio.file.Files 讀取文件僅需一行代碼實現(xiàn)
下面小編就為大家分享一篇JDK1.7 之java.nio.file.Files 讀取文件僅需一行代碼實現(xiàn),具有很好的參考價值,希望對大家有所幫助2017-11-11
SpringMVC中Model和ModelAndView的EL表達式取值方法
下面小編就為大家分享一篇SpringMVC中Model和ModelAndView的EL表達式取值方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-03-03
SpringBoot實現(xiàn)多數(shù)據(jù)源的實戰(zhàn)案例
這篇文章主要介紹了SpringBoot實現(xiàn)多數(shù)據(jù)源的實戰(zhàn)案例,文中通過示例代碼和圖文展示介紹的非常詳細,對大家的學(xué)習或工作有一定的參考學(xué)習價值,需要的朋友們下面隨著小編來一起學(xué)習吧2024-01-01
教你在?Java?中實現(xiàn)?Dijkstra?最短路算法的方法
這篇文章主要教你在?Java?中實現(xiàn)?Dijkstra?最短路算法的方法,在實現(xiàn)最短路算法之前需要先實現(xiàn)帶權(quán)有向圖,文章中給大家介紹的非常詳細,需要的朋友可以參考下2022-04-04

