Solution: Lateinit delegate

The naive solution to this problem would use null as a marker for a value that has not been initialized, but this solution would not work correctly for nullable types.

// Solution that does not work correctly for nullable types class Lateinit<T>: ReadWriteProperty<Any?, T> { var value: T? = null override fun getValue( thisRef: Any?, prop: KProperty<*> ): T = value ?: error( "Uninitialized lateinit property ${prop.name}" ) override fun setValue( thisRef: Any?, prop: KProperty<*>, value: T ) { this.value = value } }

You should replace "..." with "Uninitialized lateinit".

Correct solutions require a different way of keeping information about values that have not been initialized. It can be another property, a special flag, or an object.

class Lateinit<T>: ReadWriteProperty<Any?, T> { var value: T? = null var isInitialized = false override fun getValue( thisRef: Any?, prop: KProperty<*> ): T { if (!isInitialized) { error("Uninitialized lateinit property ${prop.name}") } return value as T } override fun setValue( thisRef: Any?, prop: KProperty<*>, value: T ) { this.value = value this.isInitialized = true } }

This solution is not thread safe. If two threads try to set the value at the same time, one thread might overwrite the value set by the other thread. To make it thread safe, we can use a synchronized block.

class Lateinit<T>: ReadWriteProperty<Any?, T> { var value: Any? = NOT_INITIALIZED override fun getValue( thisRef: Any?, prop: KProperty<*> ): T { if (value == NOT_INITIALIZED) { error("Uninitialized lateinit property ${prop.name}") } return value as T } override fun setValue( thisRef: Any?, prop: KProperty<*>, value: T ) { this.value = value } companion object { val NOT_INITIALIZED = Any() } }
class Lateinit<T> : ReadWriteProperty<Any?, T> { var value: ValueHolder<T> = NotInitialized override fun getValue( thisRef: Any?, prop: KProperty<*> ): T = when (val v = value) { NotInitialized -> error("Uninitialized lateinit property ${prop.name}") is Value -> v.value } override fun setValue( thisRef: Any?, prop: KProperty<*>, value: T ) { this.value = Value(value) } sealed interface ValueHolder<out T> class Value<T>(val value: T) : ValueHolder<T> object NotInitialized : ValueHolder<Nothing> }

Example solution in playground

import org.junit.Test import kotlin.properties.Delegates import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty import kotlin.test.assertEquals import kotlin.test.assertIs class Lateinit<T>: ReadWriteProperty<Any?, T> { var value: T? = null var isInitialized = false override fun getValue( thisRef: Any?, prop: KProperty<*> ): T { if (!isInitialized) { error("Uninitialized lateinit property ${prop.name}") } return value as T } override fun setValue( thisRef: Any?, prop: KProperty<*>, value: T ) { this.value = value this.isInitialized = true } } // Implement Lateinit delegate here // Easy: Uncomment the first 2 tests and implement Lateinit so it works for Int type // Medium: Uncomment the first 3 tests and implement Lateinit so it works for all non-nullable types // Hard: Uncomment all tests and implement Lateinit so it works for all types class LateinitTest { @Test fun `Throws exception if accessed before initialization`() { var value: Int by Lateinit() val res = runCatching { println(value) } val exception = assertIs<IllegalStateException>(res.exceptionOrNull()) assertEquals("Uninitialized lateinit property value", exception.message) var value2: Int by Lateinit() val res2 = runCatching { println(value2) } val exception2 = assertIs<IllegalStateException>(res2.exceptionOrNull()) assertEquals("Uninitialized lateinit property value2", exception2.message) } @Test fun `Behaves like a normal variable for Int`() { var value: Int by Lateinit() value = 10 assertEquals(10, value) value = 20 assertEquals(20, value) } @Test fun `Behaves like a normal variable for String`() { var value: String by Lateinit() value = "AAA" assertEquals("AAA", value) value = "BBB" assertEquals("BBB", value) } @Test fun `Behaves like a normal variable for nullable String`() { var value: String? by Lateinit() value = "AAA" assertEquals("AAA", value) value = null assertEquals(null, value) value = "BBB" assertEquals("BBB", value) } }