In Kotlin, the by keyword is used for delegation. It lets one object delegate behavior to another object instead of implementing everything manually.
There are two common forms:
- Interface/class delegation
- Property delegation
1. Interface delegation
If a class implements an interface, it can delegate the implementation of that interface to another object using by.
Example
interface Printer {
fun print(message: String)
}
class ConsolePrinter : Printer {
override fun print(message: String) {
println(message)
}
}
class LoggingPrinter(
private val printer: Printer
) : Printer by printer
Here, LoggingPrinter implements Printer, but the actual implementation is delegated to printer.
Usage:
fun main() {
val printer = LoggingPrinter(ConsolePrinter())
printer.print("Hello Kotlin")
}
Output:
Hello Kotlin
LoggingPrinter does not need to manually implement print() because Printer by printer forwards calls to the given printer.
Overriding delegated behavior
You can still override specific methods if you want custom behavior.
interface Printer {
fun print(message: String)
}
class ConsolePrinter : Printer {
override fun print(message: String) {
println(message)
}
}
class LoggingPrinter(
private val printer: Printer
) : Printer by printer {
override fun print(message: String) {
println("Logging message: $message")
printer.print(message)
}
}
Now calls to print() use the overridden method in LoggingPrinter.
fun main() {
val printer = LoggingPrinter(ConsolePrinter())
printer.print("Hello Kotlin")
}
Output:
Logging message: Hello Kotlin
Hello Kotlin
2. Delegating multiple interfaces
A class can delegate multiple interfaces to different objects.
interface Reader {
fun read(): String
}
interface Writer {
fun write(value: String)
}
class FileReader : Reader {
override fun read(): String = "file contents"
}
class ConsoleWriter : Writer {
override fun write(value: String) {
println(value)
}
}
class FileProcessor(
reader: Reader,
writer: Writer
) : Reader by reader, Writer by writer
Usage:
fun main() {
val processor = FileProcessor(FileReader(), ConsoleWriter())
val text = processor.read()
processor.write(text)
}
Important note about delegated members
If a delegated object calls one of its own methods internally, it does not dispatch to overrides in the delegating class.
interface Service {
fun operation()
fun run()
}
class DefaultService : Service {
override fun operation() {
println("Default operation")
}
override fun run() {
operation()
}
}
class CustomService(
private val service: Service
) : Service by service {
override fun operation() {
println("Custom operation")
}
}
Usage:
fun main() {
val service = CustomService(DefaultService())
service.operation()
service.run()
}
Output:
Custom operation
Default operation
service.run() is delegated to DefaultService.run(), and inside that object, operation() resolves to DefaultService.operation().
Property delegation
Property delegation lets another object provide the getter and/or setter for a property.
The syntax is:
val propertyName: Type by delegate
var propertyName: Type by delegate
The delegate object must provide:
getValue(...)forvalgetValue(...)andsetValue(...)forvar
1. Built-in property delegates
Kotlin provides several common delegates.
lazy
lazy initializes a value only when it is first accessed.
val expensiveValue: String by lazy {
println("Computing value")
"Hello"
}
fun main() {
println("Before access")
println(expensiveValue)
println(expensiveValue)
}
Output:
Before access
Computing value
Hello
Hello
The initializer runs only once.
observable
Delegates.observable runs code whenever a property changes.
import kotlin.properties.Delegates
var name: String by Delegates.observable("Unknown") { property, oldValue, newValue ->
println("${property.name} changed from $oldValue to $newValue")
}
fun main() {
name = "Alice"
name = "Bob"
}
Output:
name changed from Unknown to Alice
name changed from Alice to Bob
vetoable
Delegates.vetoable can reject a new value.
import kotlin.properties.Delegates
var age: Int by Delegates.vetoable(0) { _, _, newValue ->
newValue >= 0
}
fun main() {
age = 25
println(age)
age = -5
println(age)
}
Output:
25
25
The assignment to -5 is rejected.
notNull
Delegates.notNull() is useful for non-null properties initialized later.
import kotlin.properties.Delegates
var username: String by Delegates.notNull()
fun main() {
username = "alice"
println(username)
}
If you read username before assigning it, Kotlin throws an exception.
Custom property delegate
You can create your own delegate by implementing getValue and optionally setValue.
import kotlin.reflect.KProperty
class LoggingDelegate {
private var value: String = ""
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
println("Reading ${property.name}")
return value
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: String) {
println("Writing ${property.name}: $newValue")
value = newValue
}
}
class User {
var name: String by LoggingDelegate()
}
Usage:
fun main() {
val user = User()
user.name = "Alice"
println(user.name)
}
Output:
Writing name: Alice
Reading name
Alice
How getValue and setValue work
For:
var name: String by LoggingDelegate()
Kotlin roughly translates property access like this:
val delegate = LoggingDelegate()
delegate.setValue(thisRef, property, "Alice")
val value = delegate.getValue(thisRef, property)
The signatures are:
operator fun getValue(thisRef: Any?, property: KProperty<*>): T
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T)
For top-level properties, thisRef is null.
Delegating to another property
You can also delegate one property to another property reference.
class User {
var name: String = "Alice"
var displayName: String by this::name
}
fun main() {
val user = User()
println(user.displayName)
user.displayName = "Bob"
println(user.name)
}
Output:
Alice
Bob
displayName delegates its storage to name.
Delegating to a map
A common pattern is delegating properties to a Map.
class User(
private val data: Map<String, Any?>
) {
val name: String by data
val age: Int by data
}
Usage:
fun main() {
val user = User(
mapOf(
"name" to "Alice",
"age" to 30
)
)
println(user.name)
println(user.age)
}
Output:
Alice
30
For mutable properties, use MutableMap:
class MutableUser(
private val data: MutableMap<String, Any?>
) {
var name: String by data
var age: Int by data
}
Summary
| Use case | Syntax | Meaning |
|---|---|---|
| Interface delegation | class C(d: I) : I by d |
Forward interface calls to d |
| Lazy property | val x by lazy { ... } |
Compute once on first access |
| Observable property | var x by Delegates.observable(...) |
React to changes |
| Vetoable property | var x by Delegates.vetoable(...) |
Accept or reject changes |
| Custom delegate | var x by MyDelegate() |
Delegate getter/setter logic |
| Map-backed property | val x: T by map |
Read value from map by property name |
| Property reference | var x by this::other |
Delegate to another property |
In short:
class MyClass(delegate: SomeInterface) : SomeInterface by delegate
is for interface delegation, while:
val value by lazy { ... }
var name by MyDelegate()
is for property delegation.
