/ KOTLIN, SOFTWARE DESIGN, UML

Options for managing derived attributes in Kotlin

A derived attribute is an attribute computed from other attributes e.g.:

  • The fullName is aggregated from the first, middle and last name
  • The age is computed from the birthdate
  • etc.

Kotlin offers different options to manage such derived attributes. Let’s browse through them.

Inline field initialization

The simplest way to manage derived attributes is to declare a property, and compound its declaration with its initialization:

class Person(val firstName: String,
             val middleName: String,
             val lastName: String) {

  val name = "$firstName $middleName $lastName"
}

That works pretty well if no logic is required. But then, what if some of the properties are nullable, and null values need to be managed properly?

Initialization block

Making code more readable when logic is involved requires to move initialization into a dedicated init block:

class Person(val firstName: String? = null,
             val middleName: String? = null,
             val lastName: String? = null) {

  val name: String                                            (1)

  init {
    val fullName = """${firstName?.let { it }}
                    | ${middleName?.let { it }}
                    | ${lastName?.let { it }}""".trimMargin() (2)
    if (fullName.isBlank()) name = fullName
                       else name = "John Doe"
  }
}
1 name is a val and has to be initialized
2 Initialization takes place in the init block, hence the compiler doesn’t complain

Because the property is a val, that approach is fine when the derived attribute depends only on constants and constructor parameters. Yet, what if the valued depends on other factors, such as the current date?

Getter

For example, the above approach cannot be used to compute the age of a person, since it depends not only on the birth date, but also on the current date. The canonical way to compute such attributes in Kotlin is to use a dedicated getter method:

class Person(val birthDate: LocalDate) {

  val age: Int
    get() = birthDate.until(LocalDate.now(), ChronoUnit.DAYS).toInt()
}

Getters can easily replace both inline field initializiation and init blocks. There’s a huge difference, though: getters are methods, and might not be performant.

This approach is not suitable if the result takes time to compute, and keep returning the same result over and over.

Lazy delegate

The returned value benefits from being cached if the derived attribute:

  1. takes time to compute
  2. always return the same result over time
  3. and also - but not necessarily - needs to be accessed frequently

Kotlin offers a construct called delegated properties. To cache results, the relevant kind is the lazy delegate:

class Person(val firstName: String,
             val middleName: String,
             val lastName: String) {

  val name by lazy { computeName() }                             (1)

  private fun computeName() = "$firstName $middleName $lastName" (2)
}
1 Get the value and cache it
2 Imagine this is an extremely time-consuming operation

As expected, lazy makes it so that:

  1. if the value is never accessed, it’s never computed
  2. if it is, it’s computed the first time and cached
  3. as soon as it’s accessed a second time, the value is returned from the cache
Nicolas Fränkel

Nicolas Fränkel

Nicolas Fränkel is a Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with focused interests like Rich Internet Applications, Testing, CI/CD and DevOps. Currently working for Hazelcast. Also double as a teacher in universities and higher education schools, a trainer and triples as a book author.

Read More