
This post will explain the difference between lateinit and lazy in Kotlin
Latinit
Usage
Non-null
types can be lazy-initialized using the lateinit
keyword.
class InitTest() {
lateinit var name: String
public fun checkName(): Boolean = name.isNotEmpty()
}
Code language: JavaScript (javascript)
If it is not initialized before use, the following Exception will occur.
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)
...
Code language: CSS (css)
In order to prevent the above Exception, we can check by ::xxx.isInitializedbe
class InitTest() {
lateinit var name: String
fun checkName(): Boolean {
return if (::name.isInitialized) {
name.isNotEmpty()
} else {
false
}
}
}
Code language: JavaScript (javascript)
Init: testInit():false
Code language: JavaScript (javascript)
It can also be used normally after name has been initialized.
class InitTest() {
lateinit var name: String
fun injectName(name: String) {
this.name = name
}
fun checkName(): Boolean {
return if (::name.isInitialized) {
name.isNotEmpty()
} else {
false
}
}
}
Code language: JavaScript (javascript)
Init: testInit():true
Code language: JavaScript (javascript)
Principle
After decompilation, you can see that the variable has no @NotNull
annotation. When using it, check whether it is 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;
}
}
Code language: PHP (php)
If null
, the corresponding UninitializedPropertyAccessException
is thrown.
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 {
...
}
Code language: HTML, XML (xml)
If the variable is a non-null
type without lateinit
, it needs to be initialized when it is defined.
class InitTest() {
val name: String = "test"
public fun checkName(): Boolean = name.isNotEmpty()
}
Code language: JavaScript (javascript)
After decompilation, it is found that the variable has an @NotNull
annotation, which can be used directly.
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;
}
}
Code language: PHP (php)
As for the ::xxx.isInitialized
if it is decompiled, it can be found that the null
check is performed before use, and the preset logic is directly executed if it is empty, and vice versa, the variable is used.
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;
}
}
Code language: PHP (php)
lazy
usage
The naming of lazy is similar to lateinit, but the usage scenarios are different. It is used for lazy loading, that is, the initialization method has been determined, and it is only executed when it is used. And the modification can only be a val constant.
class InitTest {
val name by lazy {
"test"
}
public fun checkName(): Boolean = name.isNotEmpty()
}
Code language: JavaScript (javascript)
Variables decorated with lazy can be used directly without worrying about NPE.
Init: testInit():true
Code language: JavaScript (javascript)
principle
The above is the most common usage of lazy
, the code after decompilation is as follows:
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);
}
}
Code language: PHP (php)
When the instance of the class to which it belongs is created, the lazy interface type is actually assigned to the lazy variable, not the T
type. The variable will be temporarily stored as value in Lazy, and the value attribute of Lazy will be obtained when the variable is used.
The default mode of the Lazy interface is LazyThreadSafetyMode.SYNCHRONIZED
,that its default implementation is SynchronizedLazyImpl
, in which the _value
attribute is the actual value, modified with volatile.
value is read and written from _value
through get()
, get()
will first check whether _value
has not been initialized
If it has been initialized, it will be converted to T
type and returned
Instead, execute the synchronized method (by default the lock object is the impl instance) and check again if it has been initialized:
If it has been initialized, it will be converted to T
type and returned
On the contrary, execute the function initializer for initialization, the return value is stored in _value
, and return
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)
}
Code language: HTML, XML (xml)
In short, it is very similar to the way of double-checking lazy mode to get singleton in 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;
}
}
Code language: PHP (php)
lazy can also specify an internally synchronized lock object in the default SYNCHRONIZED mode above.
val name by lazy(lock) {
"test"
}
Code language: JavaScript (javascript)
lazy can also specify other modes, for example PUBLICATION, using synchronizeda CASmechanism internally.
val name by lazy(LazyThreadSafetyMode.PUBLICATION) {
"test"
}
Code language: JavaScript (javascript)
lazy can also specify NONEmode, which is not thread safe.
val name by lazy(LazyThreadSafetyMode.NONE) {
"test"
}
Code language: JavaScript (javascript)
The end
Both lateinit and lazy are used for initialization scenarios, and the usage and principles are somewhat different. To make a brief summary:
- lateinit is used as initialization for non-null types:
- Need to initialize before use
- If it is used without initialization, an UninitializedPropertyAccessException will be thrown internally.
- isInitialized Can be checked before use
- lazy is used as lazy initialization of variables:
The initializer function
It is initialized when it is used, and the held instance is returned by default through synchronization lock and double verification.
Also supports settings lock objects and other implementations mode