面试官:能说一说 Kotlin 中 lateinit 和 lazy 的区别吗?
Posted 初一十五啊
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试官:能说一说 Kotlin 中 lateinit 和 lazy 的区别吗?相关的知识,希望对你有一定的参考价值。
使用 Kotlin
进行开发,对于 latelinit
和 lazy
肯定不陌生。但其原理上的区别,可能鲜少了解过,借着本篇文章普及下这方面的知识。
为此还特意去整理了一份关于kotlin相关的文档。
涉及:对象.类.继承.变量.常量.拓展.函数等二十多个内容
一丶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
还可以指定 NONE mode
,线程不安全。
val name by lazy(LazyThreadSafetyMode.NONE)
"test"
三丶总结
lateinit
和 lazy
都是用于初始化场景,用法和原理有些区别,做个简单总结:
lateinit
用作非空类型的初始化:
- 在使用前需要初始化
- 如果使用时没有初始化内部会抛出
UninitializedPropertyAccess Exception
- 可配合
isInitialized
在使用前进行检查
lazy
用作变量的延迟初始化:
- 定义的时候已经明确了
initializer
函数体 - 使用的时候才进行初始化,内部默认通过同步锁和双重校验的方式返回持有的实例
- 还支持设置
lock
对象和其他实现mode
为此还特意去整理了一份关于kotlin相关的文档。
涉及:对象.类.继承.变量.常量.拓展.函数等二十多个内容
💡 作者:TechMerger
链接:https://juejin.cn/post/7124114861260275742
来源:稀土掘金
以上是关于面试官:能说一说 Kotlin 中 lateinit 和 lazy 的区别吗?的主要内容,如果未能解决你的问题,请参考以下文章
能说一说 Kotlin 中 lateinit 和 lazy 的区别吗?