Kotlin: lateinit to val, or, alternatively, a var that can set once
In this solution you implement a custom delegate and it becomes a separate property on your class. The delegate has a var
inside, but the controlObj
property has the guarantees you want.
class X {
private val initOnce = InitOnce<View>()
private val controlObj: View by initOnce
fun readWithoutInit() {
println(controlObj)
}
fun readWithInit() {
initOnce.initWith(createView())
println(controlObj)
}
fun doubleInit() {
initOnce.initWith(createView())
initOnce.initWith(createView())
println(controlObj)
}
}
fun createView(): View = TODO()
class InitOnce<T : Any> {
private var value: T? = null
fun initWith(value: T) {
if (this.value != null) {
throw IllegalStateException("Already initialized")
}
this.value = value
}
operator fun getValue(thisRef: Any?, property: KProperty<*>): T =
value ?: throw IllegalStateException("Not initialized")
}
BTW if you need thread safety, the solution is just slightly different:
class InitOnceThreadSafe<T : Any> {
private val viewRef = AtomicReference<T>()
fun initWith(value: T) {
if (!viewRef.compareAndSet(null, value)) {
throw IllegalStateException("Already initialized")
}
}
operator fun getValue(thisRef: Any?, property: KProperty<*>): T =
viewRef.get() ?: throw IllegalStateException("Not initialized")
}
You can implement own delegate like this:
class InitOnceProperty<T> : ReadWriteProperty<Any, T> {
private object EMPTY
private var value: Any? = EMPTY
override fun getValue(thisRef: Any, property: KProperty<*>): T {
if (value == EMPTY) {
throw IllegalStateException("Value isn't initialized")
} else {
return value as T
}
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
if (this.value != EMPTY) {
throw IllegalStateException("Value is initialized")
}
this.value = value
}
}
After that you can use it as following:
inline fun <reified T> initOnce(): ReadWriteProperty<Any, T> = InitOnceProperty()
class Test {
var property: String by initOnce()
fun readValueFailure() {
val data = property //Value isn't initialized, exception is thrown
}
fun writeValueTwice() {
property = "Test1"
property = "Test2" //Exception is thrown, value already initalized
}
fun readWriteCorrect() {
property = "Test"
val data1 = property
val data2 = property //Exception isn't thrown, everything is correct
}
}
In case when you try to access value before it is initialized you will get exception as well as when you try to reassign new value.