Skip to content
Delegated Properties: by lazy, observable
step 1/6

Reading — step 1 of 6

Learn

~2 min readCoroutines and Delegation

Delegated properties (by keyword) let a property hand off its get/set logic to another object. The standard library ships with several useful delegates; you can also write your own.

Lazy initialization with by lazy

val database by lazy {
    println("opening connection...")
    connectToDatabase()
}

fun main() {
    println("started")
    println(database)            // first access — "opening connection..." runs now
    println(database)            // second access — cached, no re-init
}

lazy { } returns a Lazy<T> that:

  1. Doesn't run the block until first access.
  2. Caches the result thereafter.
  3. Is thread-safe by default (LazyThreadSafetyMode.SYNCHRONIZED).

Perfect for expensive resources you might not need.

observable delegate

React when a property changes:

import kotlin.properties.Delegates

class User {
    var name: String by Delegates.observable("") { _, old, new ->
        println("name changed: \$old -> \$new")
    }
}

val u = User()
u.name = "Alice"     // logs: name changed:  -> Alice
u.name = "Bob"       // logs: name changed: Alice -> Bob

vetoable — reject changes

Like observable, but the callback can VETO the change by returning false:

class Score {
    var value: Int by Delegates.vetoable(0) { _, old, new ->
        new >= 0    // reject negative values
    }
}

val s = Score()
s.value = 100      // ✓ accepted
s.value = -5       // ✗ rejected; value stays 100
println(s.value)   // 100

notNull delegate

For properties that must be initialized before first read but can't be set in the constructor:

class Builder {
    var name: String by Delegates.notNull()
}

val b = Builder()
// println(b.name)   // ✗ throws — not yet initialized
b.name = "Alice"
println(b.name)      // ✓ Alice

Storing properties in a Map

Useful for parsing flexible data:

class Config(map: Map<String, Any?>) {
    val name: String by map
    val port: Int by map
}

val c = Config(mapOf("name" to "server", "port" to 8080))
println("\${c.name}:\${c.port}")

Reads pull values from the map by property name.

Custom delegates

A property delegate is anything that implements getValue (and optionally setValue):

class UpperCase {
    private var value: String = ""
    operator fun getValue(thisRef: Any?, property: kotlin.reflect.KProperty<*>): String = value
    operator fun setValue(thisRef: Any?, property: kotlin.reflect.KProperty<*>, newValue: String) {
        value = newValue.uppercase()
    }
}

class Person {
    var name: String by UpperCase()
}

val p = Person()
p.name = "alice"
println(p.name)    // "ALICE"

Roll your own when no built-in delegate fits.

Common mistakes

  • by lazy on a var — compile error. Lazy is for val only. lateinit is the var equivalent.
  • Using lateinit var for primitives — only works for non-null reference types. Use Delegates.notNull() for primitives.
  • observable callback throwing — the value still changes; the listener just errors. Be careful with side effects.
  • Map delegates with mismatched key names — silent failures or KeyNotFoundException. Test thoroughly.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…