Late initialization
Sometimes, we want to initialize the properties of the class after it is created, but in a separate member function instead of using lazy
Let’s take a look at below code. Box
interface has a function setUp
that initializes instances. Suppose we want to reuse a library that creates and manipulates Box
. This library requires subclass initialization in setUp()
instead of in a constructor.
interface Box {
fun setUp()
}
class ToyBox: Box {
private var items: String? = null
override fun setUp() {
items = "cars, trains, lego"
}
fun checkToy(toy: String): Boolean =
items?.contains(toy) ?: false
}
val toyBox = ToyBox()
toyBox.setUp()
toyBox.checkToy("lego")
As you notice, in ToyBox
class, we could not just define items
as String
because that would requires us to provide the initial value which is not null
. We may be tempted to initialize with an empty String
, but it is a bad practice because we will not know if empty is the real value of items
. We use null
to indicate that it has not yet initialized.
If we make items
as optional (String?
), it means we must check for null
in all member functions, eg. in checkToy()
, we have to make sure that items
is not null
before we could do contains()
function call. However, we know in our mind that we will call setUp()
function to initialize, so null
check is unnecessary in reality.
The lateinit
property fixes this problem. Let’s take a look at the same code with lateinit
.
interface Box {
fun setUp()
}
class ToyBox: Box {
lateinit var items: String? = null
override fun setUp() {
items = "cars, trains, lego"
}
fun checkToy(toy: String): Boolean =
toy in items
}
val toyBox = ToyBox()
toyBox.setUp()
toyBox.checkToy("lego")
Now, the checkToy()
does not need to worry if items
is going to be null
anymore. It is safely defined as non-nullable property.
Limitation
lateinit
could be used on a property inside the body of a class, a top-level property, or local var
.
However,
lateinit
could only be used onvar
property, not aval
.- The property must be non-nullable type
- The property could not be a primitive type
lateinit
could not be used used inabstract
class orinterface
lateinit
is not allowed for customget()
orset()
- The IDE would not be able to detect if we forget to initialize or there will be no compile-time error
Conclusion
The lateinit
is a better way of expression in Kotlin if we are sure that the property will be initialized later by a function, and not by constructor. It converts the property into non-nullable, which has both benefits and pitfalls. Since we would not be able to detect it during compile time, the only time that we could find out is in runtime. ::items.isInitialized
might give some helps, we should take caution about it.