Kotlin delegate Keyword
last modified April 19, 2025
Kotlin's delegation pattern allows object composition to achieve code reuse. The
by
keyword enables delegation without boilerplate code. This
tutorial explores property and class delegation with practical examples.
Basic Definitions
The by
keyword in Kotlin implements the delegation pattern. It comes
in two forms: class delegation and property delegation. Delegation helps avoid
inheritance when sharing behavior between classes.
Class Delegation
Class delegation allows implementing an interface by delegating to another object. This is useful when you want to extend functionality without inheritance.
package com.zetcode interface SoundMaker { fun makeSound() } class Dog : SoundMaker { override fun makeSound() = println("Woof!") } class Robot(private val soundMaker: SoundMaker) : SoundMaker by soundMaker { fun move() = println("Moving...") } fun main() { val dog = Dog() val robot = Robot(dog) robot.makeSound() // Output: Woof! robot.move() // Output: Moving... }
Here Robot implements SoundMaker by delegating to a Dog instance. The Robot class can focus on its own behavior while reusing Dog's sound implementation.
Lazy Property Delegation
The lazy
delegate initializes a property only when first accessed.
This is useful for expensive operations that might not always be needed.
package com.zetcode val heavyConfiguration: String by lazy { println("Computing heavy configuration") "Config loaded" } fun main() { println("Before access") println(heavyConfiguration) // Output: Computing heavy configuration println(heavyConfiguration) // Output: Config loaded (cached) }
The heavyConfiguration is computed only on first access. Subsequent accesses return the cached value. The initialization thread-safe by default.
Observable Property Delegation
The Delegates.observable
delegate allows observing property changes.
It receives the old and new values when the property changes.
package com.zetcode import kotlin.properties.Delegates class User { var name: String by Delegates.observable("<no name>") { prop, old, new -> println("$old → $new") } } fun main() { val user = User() user.name = "John" // Output: <no name> → John user.name = "Alice" // Output: John → Alice }
Each time the name property changes, the handler prints the change. This is useful for implementing change listeners without boilerplate code.
Vetoable Property Delegation
The Delegates.vetoable
delegate allows rejecting property changes.
The handler returns true to accept the change or false to reject it.
package com.zetcode import kotlin.properties.Delegates class PositiveNumber { var value: Int by Delegates.vetoable(0) { _, old, new -> new >= 0 } } fun main() { val num = PositiveNumber() num.value = 42 println(num.value) // Output: 42 num.value = -1 // Rejected println(num.value) // Output: 42 }
The vetoable delegate ensures the value stays non-negative. The change to -1 is rejected, keeping the previous value of 42.
Custom Property Delegate
You can create custom delegates by implementing the getValue and setValue operator functions. This allows complete control over property access.
package com.zetcode import kotlin.reflect.KProperty class TrimDelegate { private var trimmedValue: String = "" operator fun getValue(thisRef: Any?, property: KProperty<*>): String { return trimmedValue } operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { trimmedValue = value.trim() } } class Form { var input: String by TrimDelegate() } fun main() { val form = Form() form.input = " Hello Kotlin " println("'${form.input}'") // Output: 'Hello Kotlin' }
The TrimDelegate automatically trims whitespace from string values. The custom delegate handles both getting and setting the property value.
Map Property Delegation
Properties can be delegated to a map, with property names as keys. This is useful for dynamic property handling like JSON parsing.
package com.zetcode class User(val map: Map<String, Any?>) { val name: String by map val age: Int by map } fun main() { val user = User(mapOf( "name" to "John Doe", "age" to 30 )) println(user.name) // Output: John Doe println(user.age) // Output: 30 }
The User class delegates its properties to a map. The property names must match the map keys. This pattern is often used with JSON deserialization.
Multiple Interface Delegation
A class can delegate multiple interfaces to different objects. This allows combining behaviors from multiple sources.
package com.zetcode interface Flyer { fun fly() = println("Flying") } interface Swimmer { fun swim() = println("Swimming") } class Bird : Flyer class Fish : Swimmer class Duck(private val flyer: Flyer, private val swimmer: Swimmer) : Flyer by flyer, Swimmer by swimmer { fun quack() = println("Quack!") } fun main() { val duck = Duck(Bird(), Fish()) duck.fly() // Output: Flying duck.swim() // Output: Swimming duck.quack() // Output: Quack! }
Duck combines flying and swimming by delegating to Bird and Fish instances. It can also implement its own behavior (quacking). This avoids multiple inheritance.
Best Practices for Delegation
- Prefer composition: Use delegation over inheritance when sharing behavior between classes.
- Use standard delegates: Leverage lazy, observable, and vetoable delegates for common patterns.
- Consider readability: Custom delegates can be powerful but may reduce code clarity if overused.
- Document behavior: Clearly document any non-trivial delegation behavior in your code.
- Watch performance: Some delegates (like lazy) add small runtime overhead that might matter in hot paths.
Source
Kotlin Delegation Documentation
This tutorial covered Kotlin's by
keyword in depth, showing both
class and property delegation patterns. We explored standard delegates and
creating custom ones. Proper use of delegation can make your code more flexible
and maintainable.
Author
List all Kotlin tutorials.