Lateinit vs Lazy
Introduction
In Kotlin, lateinit and lazy are two different ways to handle the initialization of variables. The lateinit lets us initialize variables without declaring them immediately. The lazy method declares the value only if the variable is used in the program. In this article, we will clearly understand the difference and application of the two methods to handle variable initialization.
What is Lateinit?
Using the lateinit keyword in Kotlin, we can initialize a non-nullable variable without declaring it immediately. In cases where we want to avoid initializing the variable in the constructor or the beginning, we can use the keyword to initialize it later in the program.
The variable must be declared before using it otherwise it will throw the UninitializedPropertyAccessException at runtime.
This is used in situations where the value of the object is not known at the time of creating the object and will be set later in the program.
The isInitialized property is usually used with lateinit to check if they are yet initialized or not.
To declare a variable lateinit use the following syntax:
In the above code, myVariable is initialized as a String Object. This variable can later be initialized in the program.
What is Lazy?
The lazy function in Kotlin provides a method to create a variable that is initialized lazily. The property is only computed and initialized when it is accessed for the first time. This can be related to situations where we don't complete the tasks until the deadline is tomorrow.
The objects that are expensive to create and initialize and might lead to a delay in the program are initialized with the lazy keyword in Kotlin. The objects that are not used at all are not initialized increasing the efficiency of the program.
Example:
In the above code, the value of the variable myLazyProperty is only given when is accessed for the first time the program. We can add other statements to be executed in the block of code.
Comparing Lateinit and Lazy
Mutable vs Read-only:
- lateinit is used for mutable properties. Thus, it is used with var as well as val keyword and is used with mutable properties.
- On the other hand, late can only be used with val and are read-only properties.
Assignment Frequency:
- As lateinit is used with var it can be assigned multiple times in the program. The only base condition is that it should be declared before use.
- The lazy property is immutable and cannot be reassigned. Once declared, its value cannot be changed.
Primitive vs Non-primitive Datatypes:
- lazy can be used for primitive data types such as Int, Byte, Boolean, etc as well, but lateinit is not only used with non-primitive data types.
Thread Safety:
- lazy provides thread safety and thus, is suitable for situations like the implementation of the Singleton pattern where thread safety is essential.
- lateinit is not thread-safe.
Nullable vs Non-nullable:
- lateinit properties must not be null.
- lazy properties have no such restrictions, these can be declared nullable or non-nullable.
Customized Getter:
- lateinit objects cannot have a customized getter.
- lazy objects have a block of code that is to be executed when the object is called for the first time.
Access Before Initialization:
- lateinit throws UninitializedPropertyAccessException if we try to access the variable before initialization.
- Similarly, lazy cannot be accessed before its initialization. It can be nullable but it will be initialized once it is accessed.
Performance Implications
- Lateinit
- Minimal runtime overhead as they are initialized later just like regular properties.
- There must be null checks before initialization.
- There is no thread safety, requires manual synchronization.
- Lazy
- initialization cost only applies if the variable is accessed, thus potentially improving performance.
- Thread safely is included, useful for concurrent access.
- The overhead increases when the object is accessed for the first time.
Common Mistakes and Best Practices
-
Lateinit
- Common Mistakes:
- Accessing variables before initialization, leading to runtime crashes.
- Using lateinit with primitive type variables
- Common Mistakes:
-
BestPractices:
- Explicitly initialize the variable at a suitable point within the class and use after initialization only. This can be checked using the property isInitialized.
- Use nullable primitives or other methods for non-reference types.
-
Lazy
- Common Mistakes:
- Overusing lazy for frequently used properties which impacts the performance. Relying on lazy initialization with threads without manual synchronization.
- BestPractices:
- Use lazy only for expensive and rare property initialization.
- For concurrent contexts consider using thread safety in lazy initialization.
- Common Mistakes:
Applications and Examples
Example of Lateinit
Code: Let us take an example of lateinit. We will use a lateinit variable.
Output:
Explanation:
The function initializeName() initializes the name. The printName() function prints the name if it is initialized.
In the drive code, the main method creates an object first initializes the variable name, and then prints the name.
Example of Lazy
Let us understand lazy via an example. Let us say a variable requires a lot of computations and thus we use lazy with it to initialize it only when needed.
Output:
Explanation:
The variable computedValue takes the sum of the first 15 numbers. This must not be calculated if not required as a for loop will be run 15 times to the result. Thus, we declare it lazy and we can add some additional statements in the same block so we print to know when the value is initialized to the variable.
The output shows that the variable is not declared until the driver method calls the computedValue for the example object.
Applications
Lateinit
- In Android development, lateinit is valuable when injecting dependencies, such as view models, after an object is constructed. This allows for a cleaner separation of concerns.
- Use lateinit to delay the creation of resource-intensive views in Android layouts until they are needed, enhancing the overall performance of the user interface.
- When you need a non-static variable within an inner class and access to the outer class instance is not immediate, lateinit provides a way to declare and initialize the variable later. Lazy
- Utilize lazy for creating complex and resource-intensive data structures only when they are accessed. This improves application performance by deferring their construction.
- Marking database connections as lazy ensure they are established only when the actual data is needed, optimizing resource usage and preventing unnecessary connections.
- Define singleton properties with lazy to ensure thread-safe initialization and access, providing a safe and efficient way to handle singletons in concurrent environments.
- For objects involving significant processing or resources, use lazy within specific functions. This ensures the objects are initialized only when needed, minimizing unnecessary creation.
Conclusion
- The lateinit property is used when we wish to initialize the variable but not declare it immediately. The variable is declared later in the program.
- The lazy property is used with variables that require expensive calculations and are used only in specific situations. So, the variables declared lazy are initialized only when they are accessed for the first time.
- lazy is thread-safe unlike lateinit.
- lateinit required non-null non-primitive properties. lazy has no such restrictions.