Delegated properties Lazy

Lazy properties

Posted on by Andras Kindler

If you write code, chances are you already used lazy properties. The concept of lazy properties, or lazy initialization was designed to defer initialization of objects to the point when they are actually necessary. The initialization sequence runs when the object is accessed in the code, not at its declaration. Which also means that if it is never used, it is never initialized. And this translates to not running unnecessary code, a smaller memory footprint, and hence, faster execution.

The Kotlin way

Traditionally, this is achieved with overriding the getter(), where one would run the initialization code when it is invoked. However, the calculated value needs to be cached as well - we don't want our initialization logic to run multiple times. Boilerplate galore - luckily, there's a way in Kotlin to do all of the above in one single line. Enter the by lazy keyword:

private val nameTextView by lazy { 
  findViewById<TextView>(R.id.nameTextView) 
}

How does it work?

by lazy is one of the delegated properties provided in the standard library, among by observable, by vetoable, and by map. These provide extra functionality in the accessor methods, by delegating the set()/get() calls to an auxiliary property (aka. the delegated property).

This is exactly how by lazy works as well. The initialization code is ran only once, when the getter is invoked, caching the result on the way. After decompiling, it's no surprise that a delegated property of type Lazy is created, with a field for caching, and the initialization lambda specified as parameter of LazyKt.lazy(). The Lazy type conforms to Delegated properties by providing a getValue(thisRef: Any?, property: KProperty<*>) implementation. So any time our property is accessed, the delegated property's getValue() method will be called.

Threads and synchronization

If the lazy property can be accessed concurrently, from multiple different threads, you have to take care of synchronization. You can choose from three options (defined in the LazyThreadSafetyMode enum) by supplying it as a function parameter:

private val nameTextView by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { 
  findViewById<TextView>(R.id.nameTextView) 
}
  • SYNCHRONIZED - enforces single-thread initialization. This is the default setting - not supplying a value means the access will be synchronized.
  • PUBLICATION - if the property is not yet initialized, multiple concurrent calls are permitted, but the first call returning will be the value of the property.
  • NONE - no locks, no synchronization, meaning no extra overhead. Also meaning that access from multiple threads is undeterministic.

What can I use it for?

Good use cases are when the value is unknown at the moment of creating its parent, and when calculating its value is computationally intensive. Using the by lazy keyword instead of overriding the getter also improves code readability. Not to confuse with lateinit, where the purpose is similar (the property only gets initialized at a later point), but assigning the value is the responsibility of the developer.

I consider using the by lazy keyword, and more generally the lazy initialization pattern a good practice, both in terms of efficiency and code readability.