Delegation: lazy, observable, and by
Prerequisites: Interfaces, Constructors, init, and properties
Delegation means handing a job to another object. Kotlin supports it
directly with the by keyword, for both properties and whole
interfaces.
Delegated properties
import kotlin.properties.Delegates
class Settings {
val report: String by lazy {
println("Building report...")
"12 sales"
}
var theme: String by Delegates.observable("light") { _, old, new ->
println("Theme changed: $old -> $new")
}
}
fun main() {
val s = Settings()
println("Settings created")
println(s.report)
println(s.report)
s.theme = "dark"
}
Settings created
Building report...
12 sales
12 sales
Theme changed: light -> dark
by lazycomputes the value the first time it's read, then remembers it. It's thread-safe by default. Ideal for expensive values that may never be needed.Delegates.observableruns code every time the property changes.Delegates.vetoablecan reject a change.
Properties can also delegate to a map, which is handy for data parsed from configuration:
class ServerConfig(values: Map<String, Any?>) {
val host: String by values
val port: Int by values
}
fun main() {
val config = ServerConfig(mapOf("host" to "localhost", "port" to 8080))
println("${config.host}:${config.port}")
}
localhost:8080
Interface delegation
class A(inner: I) : I by inner implements interface I by forwarding
every call to inner — you only write the methods you want to change:
class CountingSet<T>(
private val inner: MutableSet<T> = mutableSetOf(),
) : MutableSet<T> by inner {
var addAttempts = 0
private set
override fun add(element: T): Boolean {
addAttempts++
return inner.add(element)
}
}
fun main() {
val tags = CountingSet<String>()
tags.add("kotlin")
tags.add("android")
tags.add("kotlin")
println("${tags.size} tags, ${tags.addAttempts} add attempts")
println("android" in tags)
}
2 tags, 3 add attempts
true
CountingSet is a full MutableSet — size, contains, remove, and
the rest all forward to inner — yet only add was written by hand. This
is composition over inheritance with no boilerplate.
Common mistakes
- Using
lateinitwhereby lazyfits better. If a value can be computed from what the object already has,lazyguarantees it's always available when read.