Learning Kotlin: The Lazy Delegate

Submitted by Robert MacLean on Tue, 08/28/2018 - 09:00

More Information

  • This is the 26th post in a multipart series.
    If you want to read more, see our series index
  • Koans used in here are 34 and 35

Following on from our introduction to the by operator and delegates, this post looks at the first of the five built-in delegates, lazy. Lazy property evaluation allows you to set the initial value of a property the first time you try to get it. This is really useful for scenarios where there is a high cost of getting the data. For example, if you want to set the value of a username which requires a call to a microservice but since you don't always need the username you can use this to initial it when you try and retrieve it the first time.

Setting up the context for the example, let us look at two ways you may do this now. The first way is you call the service in the constructor, so you take the hit immediately regardless if you ever need it. It is nice and clean though.

  1. class User {
  2.     val name: String
  3.  
  4.     constructor(id:Int){
  5.         // load service ... super expensive
  6.         Thread.sleep(1000)
  7.         name = "Robert"
  8.     }
  9. }
  10.  
  11. fun main(args:Array<String>) {
  12.     val user = User(1)
  13.     println(user.name)
  14. }

The second solution is to add a load function so we need to call that to load the data. This gets rid of the performance hit but is less clean and if you forget to call load your code is broken. You may think, that will never happen to me... I just did that right now while writing the sample code. It took me less than 2 min to forget I needed it. 🙈

Another pain is I need to make my name property variable since it will be assigned at a later point.

  1. class User(val id: Int) {
  2.     var name: String = ""
  3.  
  4.     fun load() {
  5.         // load service ... super expensive
  6.         Thread.sleep(1000)
  7.         name = "Robert"
  8.     }
  9. }
  10.  
  11. fun main(args:Array<String>) {
  12.     val user = User(1)
  13.     user.load()
  14.     println(user.name)
  15. }

The solution to this is obviously lazy - this gives us the best of all of the above (clean code, no performance hit unless I need it, can use val, no forgetting things) and none of the downsides.

  1. class User(val id: Int) {
  2.     val name: String by lazy {
  3.         // load service ... super expensive
  4.         Thread.sleep(1000)
  5.         "Robert"
  6.     }
  7. }
  8.  
  9. fun main(args:Array<String>) {
  10.     val user = User(1)
  11.     println(user.name)
  12. }