Reading — step 1 of 7
Learn
Property delegation lets a property defer its get/set to another object. Kotlin in Action treats this as one of Kotlin's signature features — it powers lazy, Delegates.observable, ViewModels, dependency injection, and more.
The by keyword
class Example {
var name: String by Delegate()
}
Reading name calls Delegate.getValue(...). Writing calls Delegate.setValue(...). The by keyword binds the property to a delegate.
Built-in: lazy
The most-used delegate in real Kotlin code:
val expensive: String by lazy {
println("computing...")
"computed result"
}
fun main() {
println("before access")
println(expensive) // "computing..." then "computed result"
println(expensive) // just "computed result" — cached
}
First access runs the lambda and caches the result. Thread-safe by default. Variants: LazyThreadSafetyMode.NONE (faster, single-threaded), LazyThreadSafetyMode.PUBLICATION (allows multiple inits, first wins).
Built-in: observable
Notify on changes:
import kotlin.properties.Delegates
class Settings {
var theme: String by Delegates.observable("light") { prop, old, new ->
println("${prop.name}: $old -> $new")
}
}
fun main() {
val s = Settings()
s.theme = "dark" // theme: light -> dark
s.theme = "sepia" // theme: dark -> sepia
}
Delegates.vetoable is similar but the lambda returns Boolean — false rejects the change.
Built-in: notNull
class User {
var email: String by Delegates.notNull()
}
val u = User()
// u.email // throws — never set
u.email = "[email protected]"
u.email // "[email protected]"
For non-null vars where you can't initialize at construction.
Map-backed properties
class Config(map: Map<String, String>) {
val baseUrl: String by map
val timeout: String by map
}
val cfg = Config(mapOf("baseUrl" to "https://api.example.com", "timeout" to "5000"))
println(cfg.baseUrl)
The Map extension provides getValue — Kotlin's stdlib makes it Just Work.
Custom delegates
Any class with the right operators can be a delegate. The signature for a read-write delegate:
import kotlin.reflect.KProperty
class UpperCaseString {
private var value: String = ""
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return value
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: String) {
value = newValue.uppercase()
}
}
class User {
var name: String by UpperCaseString()
}
fun main() {
val u = User()
u.name = "ada"
println(u.name) // ADA — delegate uppercased on set
}
For read-only properties, only getValue is needed.
Real-world delegates
- Android
viewModels()— lazy-creates a ViewModel scoped to the activity remember { }in Jetpack Compose — memoizes state across recompositions- Koin / Hilt dependency injection —
val service: Service by inject() - Database ORM —
var name: String by columns.string("name")
Common mistakes
- Forgetting
operatormodifier on getValue/setValue — required. - Calling lazy in tight loops — first call is fine; subsequent are still slightly slower than a plain val.
- Using lazy on var — doesn't make sense; lazy is for one-time computation.
- Not understanding thread mode — default is SYNCHRONIZED. NONE is faster but unsafe.
- Mixing delegate API with field-style code — once delegated, the property is just whatever the delegate decides.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…