article banner (priority)

Lazy property delegate

This is a chapter from the book Advanced Kotlin. You can find it on LeanPub.

The most popular property delegate is lazy. It implements the lazy property pattern, so it postpones read-only value initialization until the moment when this value is needed for the first time. This is what an example lazy usage looks like and what we would need to implement if we wouldn't have this delegate1:

val userRepo by lazy { UserRepository() } // Alternative code not using lazy private var _userRepo: UserRepository? = null private val userRepoLock = Any() val userRepo: UserRepository get() { synchronized(userRepoLock) { if (_userRepo == null) { _userRepo = UserRepository() } return _userRepo!! } }

The lazy delegate is often used as a performance optimization for objects that are expensive to calculate. Let's start with an abstract example. Consider class A, which composes classes B, C, and D, each heavy to initialize. This makes the instance of A really heavy to initialize because its initialization includes the initialization of multiple heavy objects.

class A { val b = B() val c = C() val d = D() // ... }

We can make A initialization lighter by making b, c, and d properties lazy. Thanks to that, their values initialization is postponed until their first use, and if those properties are never used, we will never have associated class instances initialized. Such an operation improves class creation time, which benefits application startup time and tests execution time.

class A { val b by lazy { B() } val c by lazy { C() } val d by lazy { D() } // ... }

To make this example more practical, A might be FlashcardsParser used to parse files defined in a language we invented, and B, C, and D might be some complex regex parsers, which are heavy to initialize.

class OurLanguageParser { val cardRegex by lazy { Regex("...") } val questionRegex by lazy { Regex("...") } val answerRegex by lazy { Regex("...") } // ... }

Regex is a really useful notation when we need to process text. However, regex definitions can be really complex, and their parsing is a heavy operation. This reminds me of another example I have famously shown in the Effective Kotlin book. Consider the function where we use a regex to determine whether a string contains a valid IP address:

fun String.isValidIpAddress(): Boolean { return this.matches( ("\\A(?:(?:25[0-5]|2[0-4][0-9]|" + "[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|" + "[01]?[0-9][0-9]?)\\z").toRegex() ) } // Usage print("5.173.80.254".isValidIpAddress()) // true

The problem with this function is that the Regex object needs to be created every time we use it. This is a serious disadvantage since regex pattern compilation is a complex operation. This is why this function is not suitable for repeated use in performance-constrained parts of our code. However, we can improve it by lifting the regex up to the top level:

private val IS_VALID_IP_REGEX = "\\A(?:(?:25[0-5]|2[0-4]" + "[0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|" + "[01]?[0-9][0-9]?)\\z".toRegex() fun String.isValidIpAddress(): Boolean = matches(IS_VALID_IP_REGEX)

The problem now is that this regex is initialized whenever we initialize this file for the first time, and if this file contains more elements (like other functions or properties), this regex might slow down its usage for no good reason. This is why it is better to make the IS_VALID_IP_REGEX property lazy.

private val IS_VALID_IP_REGEX by lazy { ("\\A(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}" + "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\z").toRegex() } fun String.isValidIpAddress(): Boolean = matches(IS_VALID_IP_REGEX)

This technique4 can be used on a variety of occasions. Consider a data class with a computed property3 that is not trivial to calculate. Like our User, with the fullDisplay property, which is calculated on other properties, but includes some significant logic, as it depends on multiple properties and project configurations. If we define the fullDisplay as a regular property, it will be calculated whenever a new instance of User is created, which might be an unnecessary cost.

data class User( val name: String, val surname: String, val pronouns: Pronouns, val gender: Gender, // ... ) { val fullDisplay: String = produceFullDisplay() fun produceFullDisplay(): String { println("Calculating...") // ... return "XYZ" } } fun test() { val user = User(...) // Calculating... val copy = user.copy() // Calculating... println(copy.fullDisplay) // XYZ println(copy.fullDisplay) // XYZ }

If we define fullDisplay property using getter, it will be re-calculated whenever it is used, what also might be an unnecessary cost.

data class User( val name: String, val surname: String, val pronouns: Pronouns, val gender: Gender, // ... ) { val fullDisplay: String get() = produceFullDisplay() fun produceFullDisplay(): String { println("Calculating...") // ... return "XYZ" } } fun test() { val user = User(...) val copy = user.copy() println(copy.fullDisplay) // Calculating... XYZ println(copy.fullDisplay) // Calculating... XYZ }

Defining the fullDisplay property as lazy is a sweet spot for properties that:

  • are read-only (lazy can only be used for val),
  • are non-trivial to calculate (otherwise, using lazy is not worth the effort),
  • might not be used for all instances (otherwise use the regular property),
  • might be used more than once by one instance (otherwise, define property with getter).
data class User( val name: String, val surname: String, val pronouns: Pronouns, val gender: Gender, // ... ) { val fullDisplay: String by lazy { produceFullDisplay() } fun produceFullDisplay() { println("Calculating...") // ... } } fun test() { val user = User(...) val copy = user.copy() println(copy.fullDisplay) // Calculating... XYZ println(copy.fullDisplay) // XYZ }

When we consider lazy delegate as a performance optimization, we should also consider what thread safety mode we want it to use. It can be specified in an additional function argument mode accepting the enum LazyThreadSafetyMode. There are the following options:

  • SYNCHRONIZED is the default and the safest option that uses locks to ensure that only a single thread can initialize this delegate instance. This option is also the slowest, as synchronization mechanisms introduce some performance costs.
  • PUBLICATION means that the initializer function can be called several times on concurrent access to an uninitialized delegate instance value, but only the first returned value will be used as the value of this delegate instance. If a delegate is used by only a single thread, this option will be slightly faster than SYNCHRONIZED, but when used by multiple threads, we need to cover the possible cost of the same value recalculation before the value is initialized.
  • NONE is the fastest option, which uses no locks to synchronize access to the delegate instance value, so if the instance is accessed from multiple threads, its behavior is undefined. This mode should not be used unless this instance is guaranteed never to be initialized from more than one thread.
val v1 by lazy { calculate() } val v2 by lazy(LazyThreadSafetyMode.PUBLICATION) { calculate() } val v3 by lazy(LazyThreadSafetyMode.NONE) { calculate() }

In all the previous examples, we used lazy as a performance optimization, but this is not the only reason. With that, I would like to share a story from my early days of using Kotlin on Android. It was around 2015. Kotlin was still before the stable release, and the Kotlin community was inventing ways how its features can be used to help us with everyday tasks. You see, in Android, we have a concept of Activity, which is like a window that defines its view, traditionally using XML files. Then the class representing Activity often modified this view by programmatically changing texts or some of its properties. To change a particular view element, we need to reference it, but we cannot reference it before the view is set with the setContentView function. That is why back then, it was a standard to define references to view elements as lateinit properties and associate proper view elements to them straight after setting content view.

class MainActivity : Activity() { lateinit var questionLabelView: TextView lateinit var answerLabelView: EditText lateinit var confirmButtonView: Button override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) questionLabelView = findViewById(R.id.main_question_label) answerLabelView = findViewById(R.id.main_answer_label) confirmButtonView = findViewById(R.id.main_button_confirm) } }

This pattern was used in nearly all Android applications (and I can still see it nowadays in some projects), even though it is far from perfect. We need to define multiple lateinit properties. They are all read-write, even though they are initialized only once. Property definition and assignment are separated. The property name repeats. If a property is not used, it will not be marked by IDE because the assignment is considered a usage. Overall, there is a lot of space for improvement. The solution turned out to be extremely simple: We can define property initialization next to its definition if we make it lazy.

class MainActivity : Activity() { val questionLabelView: TextView by lazy { findViewById(R.id.main_question_label) } val answerLabelView: TextView by lazy { findViewById(R.id.main_answer_label) } val confirmButtonView: Button by lazy { findViewById(R.id.main_button_confirm) } override fun onCreate(savedInstanceState: Bundle) { super.onCreate(savedInstanceState) setContentView(R.layout.main_activity) } }

Thanks to the lazy delegate, finding the view by id will be postponed until the property is used for the first time, and we can assume it will happen after the content view is set. We made such an assumption in the previous solution, so why not make it in this one as well? We also have some important improvements: Property is read-only, initialization and definition are kept together, and unused properties will be marked. We also have performance benefits: View reference that is never used will never be associated with the view element, and findViewById execution can be expensive.

What is more, we can push this pattern further and extract a function that will both define a lazy delegate and find view by id. In the project I co-created, we named it bindView, and it helped us make our view references really clear, consistent, and readable.

class MainActivity : Activity() { var questionLabelView: TextView by bindView(R.id.main_question_label) var answerLabelView: TextView by bindView(R.id.main_answer_label) var confirmButtonView: Button by bindView(R.id.main_button_confirm) override fun onCreate(savedInstanceState: Bundle) { super.onCreate(savedInstanceState) setContentView(R.layout.main_activity) } } // ActivityExt.kt fun <T : View> Activity.bindView(viewId: Int) = lazy { this.findViewById<T>(viewId) }

We started using this pattern to bind other references as well, like strings or colors. But my favorite part is binding Activity arguments. I still use this in some of my projects. This is really convenient to have all activity properties defined together with their key at the top of this activity definition.

class SettingsActivity : Activity() { private val titleString by bindString(R.string.title) private val blueColor by bindColor(R.color.blue) private val doctor by extra<Doctor>(DOCTOR_KEY) private val title by extraString(TITLE_KEY) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.settings_activity) } } // ActivityExt.kt fun <T> Activity.bindString(@IdRes id: Int): Lazy<T> = lazy { this.getString(id) } fun <T> Activity.bindColor(@IdRes id: Int): Lazy<T> = lazy { this.getColour(id) } fun <T : Parcelable> Activity.extra(key: String) = lazy { this.intent.extras.getParcelable(key) } fun Activity.extraString(key: String) = lazy { this.intent.extras.getString(key) }

In the backend application, you can also find lazy delegates used for initializing properties that should not be initialized during project setup, but when they are needed for the first time. A simple example is a connection to a local database.

As you can see, lazy is a powerful delegate that is mainly used for performance optimization but can also be used to simplify our code. I would like to finish this section with a small puzzler. Consider the following code2:

class Lazy { var x = 0 val y by lazy { 1 / x } fun hello() { try { print(y) } catch (e: Exception) { x = 1 print(y) } } } fun main(args: Array<String>) { Lazy().hello() }

What will be printed? Try to answer this question before reading any further.

To understand the answer, we need to consider three facts:

  • Delegate exceptions are propagated out of accessors.
  • Lazy delegate first tries to return previously calculated value, and if there is no value stored, it uses a lambda expression to calculate it. If the calculated process is disturbed with an exception, no value is stored, and when we ask for the lazy value the next time, processing starts again.
  • Kotlin's lambda expressions are automatically capturing variable references, so when we use x inside the lambda expression, every time we use it, we will receive the current value of x, so when we call the lambda expression for the second time, x is 1, so the result should be 1.

So the answer to this puzzler is "1".

In the next part of this series, I will show you observable and vetoable, that are other amazing property delegates. So stay tuned.

1:

The actual lazy implementation is more complicated, as it has better synchronization securing it for concurrent use.

2:

I first heard about this puzzler from Anton Keks' presentation, and it can be found in his repository, so I assume he is the author.

3:

By computed property, I mean a property whose value is determined by other properties, so can always be recalculated.

4:

Also known as memoization.