当前位置:网站首页>能说一说 Kotlin 中 lateinit 和 lazy 的区别吗?
能说一说 Kotlin 中 lateinit 和 lazy 的区别吗?
2022-07-27 12:48:00 【TechMerger】

使用 Kotlin 进行开发,对于 latelinit 和 lazy 肯定不陌生。但其原理上的区别,可能鲜少了解过,借着本篇文章普及下这方面的知识。
lateinit
用法
非空类型可以使用 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
用法
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"
}
the end
lateinit 和 lazy 都是用于初始化场景,用法和原理有些区别,做个简单总结:
lateinit 用作非空类型的初始化:
- 在使用前需要初始化
- 如果使用时没有初始化内部会抛出
UninitializedPropertyAccessException - 可配合
isInitialized在使用前进行检查
lazy 用作变量的延迟初始化:
- 定义的时候已经明确了
initializer函数体 - 使用的时候才进行初始化,内部默认通过同步锁和双重校验的方式返回持有的实例
- 还支持设置
lock对象和其他实现mode
references
边栏推荐
- Article reproduction: srcnn
- Gartner authority predicts eight development trends of network security in the next four years
- W3School导航栏练习
- pg同步多张数据表至mysql 有办法简化配置吗?
- Xianghe meat cake in memory
- 爱可可AI前沿推介(7.27)
- POJ2594 Treasure Exploration【二分图最小路径覆盖】【Floyd】
- 力扣 1480. 一维数组的动态和 383. 赎金信412. Fizz Buzz
- heap
- 2021-03-15
猜你喜欢

接口测试实战教程01:接口测试环境搭建

程序员培训学习后好找工作吗

18. Text processing tool -grep

Initializing database error after reinstalling MySQL

2022 global Vocational Education Industry Development Report

Set interface

The sparksubmit. Main () method submits external parameters and remotely submits the standalone cluster task

multi-table query

Article reproduction: srcnn

From the perspective of it, the CIO of B2B industry talks about how to change from "cost center" to "growth center"?
随机推荐
Seata's landing practice in ant International Banking
分布式系统架构理论与组件
SparkSubmit.main()方法提交外部参数,远程提交standalone集群任务
B站713故障后的多活容灾建设|TakinTalks大咖分享
C语言犄角旮旯的知识之数组与函数
feign的动态代理
粘制定位
计算字符串最后一个单词的长度,单词以空格隔开。
Plus SBOM: assembly line BOM pbom
flinksql从Oracle同步数据到Doris,一共50几个字段,Oracle表中3000多万条
"Game engine light in light out" 4.1 unity shader and OpenGL shader
JS date and time format (year, month, day, hour, minute, second, week, quarter, time difference acquisition, date and timestamp conversion function)
马斯克被曝绿了谷歌创始人:导致挚友二婚破裂,曾下跪求原谅
v-on基础指令
Feign的两个调用处理器
完美指南|如何使用 ODBC 进行无代理 Oracle 数据库监控?
力扣 1480. 一维数组的动态和 383. 赎金信412. Fizz Buzz
Nodejs body parser middleware processes post form data of type multipart / form data, and req.body cannot receive data
Multi activity disaster recovery construction after 713 failure of station B | takintalks share
JS single thread understanding notes - Original