Kotlin Lazy vs Lateinit Properties. When to use which property?
Kotlin provides many great features. We can leverage these features and build a high-quality application quickly. Among all those features, lateinit
and lazy
are important initialization properties. It is necessary to know when to use lateinit
and when to use lazy
initialization.
‘lateinit’
There are times when a variable’s value is not available at the site of its declaration. An obvious example of this for Android developers is a UI widget used in an Activity
or Fragment
. It is not until the onCreate
or onCreateView
method runs that the variable, used throughout the activity to refer to the widget, can be initialized. The submitButton
in this example, for instance:
Example 1.1 object initialization without lateint
class HomeFragment: Fragment() {
// we will provide actual value later
private var submitButton: Button? = null
}
The variable must be initialized. A standard technique, since we can’t know the value yet, is to make the variable nullable and initialize it with null
.
However, the problem with using a nullable type is that whenever you use submitButton
in your code, you will have to check for nullability. For example: submitButton?.setOnClickListener { .. }
. A couple of variables like this and you’ll end up with a lot of annoying question marks! This can look particularly cluttered if you are used to Java and its simple dot notation.
Why, you might ask, does Kotlin prevent me from declaring the submitButton
using a non-null type when you are sure that you will initialize it before anything tries to access it?
It’s possible. You can do exactly that using the lateinit
modifier, as shown in the below code:
Example 1.2 object initialization with lateint
class HomeFragment: Fragment() {
private lateinit var submitButton: Button // will initialize later
}
Because the variable is declared lateinit
, Kotlin will let you declare it without assigning it a value. The variable must be mutable, a var
, because, by definition, you will assign a value to it, later. Great—problem solved, right?
When you use lateinit
, you’re telling the compiler, “I don’t have a value to give you right now. But I’ll give you a value later, I promise.” If something goes wrong, it’s on you.”
By using the lateinit
modifier, you disable Kotlin’s null safety for your variable. If you forget to initialize the variable or try to call some method on it before it’s initialized, you’ll get UninitializedPropertyAccessException
, which is essentially the same as getting NullPointerException
in Java.
When to use lateinit
initialization
- If a variable is mutable and can be initialized at a later stage.
- You are sure about initializing a variable before using it.
- using
var
keyword.
Lazy Properties
It’s a common pattern in software engineering to put off creating and initializing an object until it is actually needed. This pattern is known as lazy initialization, and is especially common on Android, since allocating a lot of objects during app startup can lead to a longer startup time. Below is a typical case of lazy initialization in Java.
Example 2.1 Java lazy initialization
class MyClass {
private HeavyObject heavy; public HeavyObject getHeavy() {
if (heavy == null) {
heavy = new HeavyObject();
}
return heavy;
}
}
The field heavy
is initialized with a new instance of the class HeavyObject
(which is, presumably, expensive to create) only when its value is first requested with a call, for example, to myClass.getHeavy()
. Subsequent calls to getHeavy()
will return the cached instance.
In Kotlin, lazy initialization is a part of the language. By using the directive by lazy
and providing an initialization block, the rest of the lazy instantiation is implicit, as shown below:
Example 2.2 Kotlin lazy initialization
class MyClass {
val heavy by lazy { // Initialization block
HeavyObject()
}
}
Notice that the code in Example 2.1 isn’t thread-safe. Multiple threads calling MyClass
’s getHeavy()
method simultaneously might end up with different instances of Heavyweight
.
By default, the code in Example 2.2 is thread-safe. Calls to MyClass::getHeavy()
will be synchronized so that only one thread at a time is in the initialization block.
Fine-grained control of concurrent access to a lazy initialization block can be managed using LazyThreadSafetyMode
.
A Kotlin lazy value will not be initialized until a call is made at runtime. The first time the property heavy
is referenced, the initialization block will be run.
When to use Lazy
initialization
- Variable will not be initialized unless you call it.
- Initializes the variable once; that same value is then used throughout the code.
- Used in case of
val
property i.e. read-only properties as the same object will be shared throughout the program.
That's all folks. Stay tuned for upcoming articles. For any quires or suggestions, feel free to hit me on LinkedIn. Thank you !!!
Keep Exploring, Keep Learning, Keep Growing