Delegation Workbook
Nine short exercises on Kotlin delegation — class delegation with by, lazy and observable properties, map-backed properties, writing a custom delegate, and lateinit versus lazy.
Practice problems for The by Keyword: Delegation Without the Boilerplate. Each takes a minute or two. Every exercise comes with a runnable editor — fill in the TODO, press Run to compile and run it on JetBrains’ Kotlin compiler server, then click Show answer to check yourself. Nothing here is a trick question, just direct practice of the syntax from the lesson.
class delegation
1. Delegate an interface
Write class LoggingList<T>(private val inner: MutableList<T>) : MutableList<T> that forwards everything to inner but prints before each add.
class LoggingList<T>(
private val inner: MutableList<T> = mutableListOf()
) : MutableList<T> by inner {
// TODO: override add(element) to print before delegating to inner.add
}
fun main() {
val list = LoggingList<String>()
list.add("hello")
println(list)
} Show answer Hide answer
class LoggingList<T>(
private val inner: MutableList<T> = mutableListOf()
) : MutableList<T> by inner {
override fun add(element: T): Boolean {
println("adding $element")
return inner.add(element)
}
}by inner generates every forwarding method; you override only what you want to change.
2. Delegate, then add methods
Write a Stack<T> that delegates MutableList<T> behavior to an inner list and adds push(x) and pop().
class Stack<T>(
private val items: MutableList<T> = mutableListOf()
) : MutableList<T> by items {
// TODO: add push(x) and pop() backed by items
}
fun main() {
val stack = Stack<Int>()
stack.add(1)
println(stack)
} Show answer Hide answer
class Stack<T>(
private val items: MutableList<T> = mutableListOf()
) : MutableList<T> by items {
fun push(x: T) = items.add(x)
fun pop(): T = items.removeLast()
}by items forwards the whole MutableList surface; you write only what’s new — and it works even though items is composed, not inherited.
delegated properties
3. Compute once, lazily
Declare a val config that loads from disk on first access and caches the result.
data class Config(val name: String)
fun loadConfigFromDisk(): Config {
println("loading from disk...")
return Config("prod")
}
// TODO: declare 'config' so loadConfigFromDisk() runs only on first access (by lazy)
val config: Config = loadConfigFromDisk()
fun main() {
println(config)
println(config)
} Show answer Hide answer
val config: Config by lazy {
loadConfigFromDisk()
}The block runs at most once, on first read.
4. React to changes
Declare a var name that prints "<old> -> <new>" after each assignment.
import kotlin.properties.Delegates
// TODO: make 'name' print the old and new values after each assignment (Delegates.observable)
var name: String = ""
fun main() {
name = "Ada"
name = "Grace"
println(name)
} Show answer Hide answer
import kotlin.properties.Delegates
var name: String by Delegates.observable("") { _, old, new ->
println("$old -> $new")
} 5. Back a property with a map
Declare class Config(source: Map<String, Any?>) with a val host: String and val port: Int read from the map by their own names.
class Config(private val source: Map<String, Any?>) {
// TODO: read host: String and port: Int from source by their own names (by source)
}
fun main() {
val config = Config(mapOf("host" to "localhost", "port" to 8080))
// TODO: after adding host and port, print them
println("config created")
} Show answer Hide answer
class Config(private val source: Map<String, Any?>) {
val host: String by source
val port: Int by source
} custom delegates
6. Write a delegate
Write a delegate UpperCase so that var name: String by UpperCase() stores every assignment uppercased.
import kotlin.reflect.KProperty
class UpperCase {
private var stored = ""
operator fun getValue(thisRef: Any?, property: KProperty<*>) = stored
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
// TODO: store the value uppercased
stored = value
}
}
var name: String by UpperCase()
fun main() {
name = "hello"
println(name)
} Show answer Hide answer
import kotlin.reflect.KProperty
class UpperCase {
private var stored = ""
operator fun getValue(thisRef: Any?, property: KProperty<*>) = stored
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
stored = value.uppercase()
}
} 7. A read-only delegate
Write the minimal delegate Constant42 so that val answer: Int by Constant42() always reads as 42.
import kotlin.reflect.KProperty
class Constant42 {
// TODO: add a getValue that always returns 42
operator fun getValue(thisRef: Any?, property: KProperty<*>) = 0
}
val answer: Int by Constant42()
fun main() {
println(answer)
} Show answer Hide answer
import kotlin.reflect.KProperty
class Constant42 {
operator fun getValue(thisRef: Any?, property: KProperty<*>) = 42
}
val answer: Int by Constant42()A read-only (val) delegate needs only getValue; a var would also need setValue.
lateinit vs lazy
8. Assigned from outside
You have a non-null var set by a framework after construction, not computed by the object itself. Which tool — and declare it for a repository: UserRepository.
class UserRepository
// TODO: declare 'repository' as a non-null var a framework assigns later (lateinit)
var repository: UserRepository = UserRepository()
fun main() {
// pretend a framework assigns it after construction
repository = UserRepository()
println(repository)
} Show answer Hide answer
lateinit var repository: UserRepositorylateinit — for a non-null var something else assigns later. lazy is for a val that computes itself.
9. Reject bad assignments
Declare a var age that refuses negative values (keeping the previous value) using Delegates.vetoable.
import kotlin.properties.Delegates
// TODO: make 'age' reject negative assignments, keeping the previous value (Delegates.vetoable)
var age: Int = 0
fun main() {
age = 30
age = -5 // should be rejected once solved
println(age)
} Show answer Hide answer
import kotlin.properties.Delegates
var age: Int by Delegates.vetoable(0) { _, _, new -> new >= 0 }vetoable runs before the assignment and rejects it when the handler returns false.
Going deeper: lazy and a custom delegate
12. Compute once, cache forever
Give expensive a value that runs its initializer only on first read. The test checks the block runs exactly once across two reads.
import org.junit.Test
import org.junit.Assert
var calls = 0
class Box {
//sampleStart
val expensive: Int = run { calls++; 99 } // TODO: make this lazy so calls stays 1
//sampleEnd
}
class Test {
@Test fun deferredAndCachedOnce() {
calls = 0
val b = Box()
Assert.assertEquals("not run until first read", 0, calls)
Assert.assertEquals(99, b.expensive)
Assert.assertEquals(99, b.expensive)
Assert.assertEquals("run exactly once", 1, calls)
}
} Show answer Hide answer
val expensive: Int by lazy { calls++; 99 }by lazy { } runs the block on first access and caches the result; later reads return the cached value without re-running it. (It’s thread-safe by default; pass LazyThreadSafetyMode.NONE when the property is single-threaded.)
13. A transforming delegate
Write a delegate so that assigning to name stores it uppercased. Implement getValue/setValue.
import org.junit.Test
import org.junit.Assert
import kotlin.reflect.KProperty
class Test {
@Test fun upper() {
val u = User()
u.name = "ada"
Assert.assertEquals("ADA", u.name)
}
}
//sampleStart
class UpperCase {
private var stored = ""
// TODO: getValue returns stored; setValue stores value.uppercase()
operator fun getValue(thisRef: Any?, property: KProperty<*>): String = stored
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { stored = value }
}
class User { var name: String by UpperCase() }
//sampleEnd Show answer Hide answer
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
stored = value.uppercase()
}by wires the property’s reads and writes to the delegate’s getValue/setValue. The property argument is a KProperty carrying metadata (its name), so one delegate can serve many properties.
Back to the lesson, The by Keyword, or on to the next one: generics.
Comments