Since object creation is a heavy process as it initialises all the public and private properties defined in that class when the constructor is called, Kotlin has few ways to initialise properties later when required. We already discussed lateinit properties and lazy properties.
Let’s try to understand some basic differences between then and when to use what. But before that let’s quickly recap the lateinit and lazy properties
lateinit property
lateinit properties are the var
properties that can be initialised later in the constructor or in any function according to the use.
data class User (val id : Long,
val username : String) : Serializable
lateinit var lateinitUser : User
lazy property
lazy properties are the val
properties that can also be initialised later when they are called the first time.
val lazyUser : User? by lazy {
User(id = 1, username = "agrawalsuneet")
}
Now lets try to understand difference between them.
lateinit var whereas lazy val
lateinit can only be used with a var
property whereas lazy will always be used with val
property.
A lateinit property can be reinitialised again and again as per the use whereas the lazy property can only be initialised once.
lateinit var lateinitUser : User
val lazyUser : User? by lazy {
User(id = 1, username = "agrawalsuneet")
}
lateinit can’t have custom getter or setter whereas lazy has custom getter
A lateinit property can’t have a custom getter whereas a lazy property has a block that gets executed whenever the first time that property is called.
val lazyUser : User by lazy {
//can do other initialisation here
User(id = 1, username = "agrawalsuneet")
}
isInitialized
In order to check if a lateinit property is initialised or not, we can use the extension function to KProperty
directly which returns a boolean
if the property is initialised or not.
/**
* Returns `true` if this lateinit property has been assigned a value, and `false` otherwise.
*
* Cannot be used in an inline function, to avoid binary compatibility issues.
*/
@SinceKotlin("1.2")
@InlineOnly
inline val @receiver:AccessibleLateinitPropertyLiteral KProperty0<*>.isInitialized: Boolean
get() = throw NotImplementedError("Implementation is intrinsic")
println(::lateinitUser.isInitialized)
Since the lazy block returns an object which implements the Lazy interface, we can check if the variable is initialised or not in the case of the lazy property also but for that, we need to split the lazy call.
val lazyUserDelegate = lazy { User(id = 1, username = "agrawalsuneet") }
val lazyUser by lazyUserDelegate
println(lazyUserDelegate.isInitialized())
Primitive types
lateinit properties can’t be of primitive data types whereas lazy properties can be of primitive date types also.
lateinit var lateinitInt : Int <e>//compilation error: 'lateinit' modifier is not allowed on properties of primitive types</e>
val lazyInt by lazy {
10
}
Thread Safety
We can’t define the thready safety in case of lateinit property but in case of lazy, we can choose between SYNCHRONIZED
, PUBLICATION
and NONE
.
val lazyUser : User? by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
User(id = 1, username = "agrawalsuneet")
}
Nullable Type
A lazy property can be of nullable type but a lateinit property can’t be of nullable type.
lateinit var lateinitUser : User? <e>//compilation error: 'lateinit' modifier is not allowed on properties of nullable types</e>
val lazyUser : User? by lazy {
User(id = 1, username = "agrawalsuneet")
}
Accessing before initialisation
Accessing a lateinit property before it has been initialized throws a special exception that clearly identifies the property being accessed and the fact that it hasn’t been initialized.
We can’t ever access a lazy property before it hased been initialised. Remember that the lazy property can be null but the initialisation will still happen when the first time the property will be called.
Based on the above difference we can decide when to use the lateinit property or when to use the lazy property. The developer doesn’t have to remember if the property is initialised or not in case of lazy property but in case of lateinit we need to remember the flow.
There are no guidelines in order to decide when to use what but according to the use case, we can decide.