In Kotlin if you don't want to initiate a class property inside the constructor or in the top of the class body, you have basically these two options ( from the language reference):
- Lazy Initialization
lazy() is a function that takes a lambda and returns an instance of Lazy which can serve as a delegate for implementing a lazy property: the first call to get() executes the lambda passed to lazy() and remembers the result, subsequent calls to get() simply return the remembered result.
Example
public class Hello {
val myLazyString: String by lazy { "Hello" }
}
So the first call and the subquential calls, wherever it is, to myLazyString will return "Hello"
- Late Initialization
Normally, properties declared as having a non-null type must be initialized in the constructor. However, fairly often this is not convenient. For example, properties can be initialized through dependency injection, or in the setup method of a unit test. In this case, you cannot supply a non-null initializer in the constructor, but you still want to avoid null checks when referencing the property inside the body of a class.
To handle this case, you can mark the property with the lateinit modifier:
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() { subject = TestSubject() }
@Test fun test() { subject.method() }
}
The modifier can only be used on var properties declared inside the body of a class (not in the primary constructor), and only when the property does not have a custom getter or setter. The type of the property must be non-null, and it must not be a primitive type.
So, how to choose correctly between these two options, since both of them can solve the same problem ?
Here are the significant differences between lateinit var
and by lazy { ... }
delegated property:
lazy { ... }
delegate can only be used for val
properties, whereas lateinit
can only be applied to var
s, because it can't be compiled to a final
field, thus no immutability can be guaranteed;
lateinit var
has a backing field which stores the value, and by lazy { ... }
creates a delegate object in which the value is stored once calculated, stores the reference to the delegate instance in the class object and generates the getter for the property that works with the delegate instance. So if you need the backing field present in the class, use lateinit
;
In addition to val
s, lateinit
cannot be used for nullable properties and Java primitive types (this is because of null
used for uninitialized value);
lateinit var
can be initialized from anywhere the object is seen from, e.g. from inside a framework code, and multiple initialization scenarios are possible for different objects of a single class. by lazy { ... }
, in turn, defines the only initializer for the property, which can be altered only by overriding the property in a subclass. If you want your property to be initialized from outside in a way probably unknown beforehand, use lateinit
.
Initialization by lazy { ... }
is thread-safe by default and guarantees that the initializer is invoked at most once (but this can be altered by using another lazy
overload). In case of lateinit var
, it's up to the user's code to initialize the property correctly in multi-threaded environments.
A Lazy
instance can be saved, passed around and even used for multiple properties. On contrary, lateinit var
s do not store any additional runtime state (only null
in the field for uninitialized value).
If you hold a reference to an instance of Lazy
, isInitialized()
allows you to check whether it has already been initialized (and you can obtain such instance with reflection from a delegated property). To check whether a lateinit property has been initialized, you can use property::isInitialized
since Kotlin 1.2.
A lambda passed to by lazy { ... }
may capture references from the context where it is used into its closure.. It will then store the references and release them only once the property has been initialized. This may lead to object hierarchies, such as Android activities, not being released for too long (or ever, if the property remains accessible and is never accessed), so you should be careful about what you use inside the initializer lambda.
Also, there's another way not mentioned in the question: Delegates.notNull()
, which is suitable for deferred initialization of non-null properties, including those of Java primitive types.
Additionnally to hotkey
's good answer, here is how I choose among the two in practice:
lateinit
is for external initialisation: when you need external stuff to initialise your value by calling a method.
e.g. by calling:
private lateinit var value: MyClass
fun init(externalProperties: Any) {
value = somethingThatDependsOn(externalProperties)
}
While lazy
is when it only uses dependencies internal to your object.
Very Short and concise Answer
lateinit: It initialize non-null properties lately
Unlike lazy initialization, lateinit allows the compiler to recognize that the value of the non-null property is not stored in the constructor stage to compile normally.
lazy Initialization
by lazy may be very useful when implementing read-only(val) properties that perform lazy-initialization in Kotlin.
by lazy { ... } performs its initializer where the defined property is first used, not its declaration.
Credit goes to @Amit Shekhar
lateinit
lateinit is late initialization.
Normally, properties declared as having a non-null type must be initialized in the constructor. However, fairly often this is not convenient. For example, properties can be initialized through dependency injection, or in the setup method of a unit test. In this case, you cannot supply a non-null initializer in the constructor, but you still want to avoid null checks when referencing the property inside the body of a class.
Example:
public class Test {
lateinit var mock: Mock
@SetUp fun setup() {
mock = Mock()
}
@Test fun test() {
mock.do()
}
}
lazy
lazy is lazy initialization.
lazy() is a function that takes a lambda and returns an instance of lazy which can serve as a delegate for implementing a lazy property: the first call to get() executes the lambda passed to lazy() and remembers the result, subsequent calls to get() simply return the remembered result.
Example:
public class Example{
val name: String by lazy { “Amit Shekhar” }
}
If you are using Spring container and you want to initialize non-nullable bean field, lateinit
is better suited.
@Autowired
lateinit var MyBean myBean