I would like to have a Kotlin Language Feature that allows fully type-safe use of an immutable properties in classes that requires an empty constructure plus a method that initialises the lifecycle of the object (for example an Android Activity with the method onCreate).
I read a few Topics mentioning this kind of feature and understand it’s currently not possible to implement it on compiler level.
The Solution Suggested:
The solution would be to add a Delegator into the standard library that implements this behaviour. The Delegator could be added to the kotlin.properties.Delegates object and could look something like this:
class InitOnce<T>(default: T? = null, val allowedChanges: Int = 1): ReadWriteProperty<Any, T> {
private var changeCnt: Int = 0
private var value: T? = default
override fun getValue(thisRef: Any, property: KProperty<*>): T {
@Suppress("UNCHECKED_CAST")
return value as T
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
if (changeCnt >= allowedChanges)
throw IllegalStateException("Property can only be set $allowedChanges times!")
this.value = value
changeCnt++
}
}
I would be very interested in your opinion about this suggestion.
Thank you
Usually the constructor is what does once-and-for-all setting of fields. Android is a bit weird, but that’s because they designed the framework in such a way that some classes are initialized by the framework itself.
If you’re forced into a scenario where you need to have classes created by some framework, then I guess your solution should work
I don’t think it is possible in general to do this unless you add a much more complex (stateful) type system. You would need to annotate functions as requiring/updating state and particular states promising certain properties. This would be very invasive to the language, an interesting experiment indeed, but it probably doesn’t belong in Kotlin.
If your goal is a lateinit val, you could definitely achieve it with delegation (and overriding the getter).
The most direct method I can think of is a lateinit backing field:
class Foo() {
private lateinit var _value: String
val value: String by ::_value
fun initFields() {
_value = "initialized"
}
}
fun main() {
//sampleStart
val f = Foo()
//println(f.value) // Try uncommenting this line.
f.initFields()
println(f.value)
//sampleEnd
}
You could also use a delegate class as you propose and keep a reference to the delegate in order to set the value. This solution almost the same as a backing property but instead of delegating to a property.
If we need more complex assignment logic like limiting to a specific number of internal assignments, we can override the getter or use an intermediate delegate instead of directly delegating to the backing property.
Here’s another possibly simpler take on non-null val that is late initialized:
class Foo() {
private var _value: String? = null
val value: String get() = _value ?: error("value not initialized!")
fun initFields() {
_value = "initialized"
}
}
fun main() {
//sampleStart
val f = Foo()
//println(f.value) // Try uncommenting this line.
f.initFields()
println(f.value)
//sampleEnd
}