'Kotlin, set var/val once to make it final, is that possible
In Kotlin, we have val that is final and can't be change. e.g.
val something = "Something"
If a value that is is initialized later, we use lateinit var.
lateinit var something: String
But this is var instead of val. I wanted to set something once (not in constructor), and have it as final. How could I achieve this?
Solution 1:[1]
Reading into the conventions of Kotlin, a late-initialized variable which is final is impossible.
Consider its use case:
Normally, properties declared as having a non-null type must be initialized in the constructor. However, fairly often this is not convenient. For example, properties can be initialized through dependency injection, or in the setup method of a unit test. In this case, you cannot supply a non-null initializer in the constructor, but you still want to avoid null checks when referencing the property inside the body of a class.
lateinit var is providing relative sanity when dealing with a variable that may not have yet been initialized, such as the case with injected fields (like Spring and @Autowired). Then, speaking strictly in the context of dependency injection, if you don't have a way to concretely instantiate the variable at compile time, then you cannot leave it as a final field.
From a Java to Kotlin world, having a late initialized variable come in as final would look as paradoxical as this from Spring:
@Autowired
private final Interface something;
Solution 2:[2]
What do you think the behavior should be when you attempt to set it again? Do you expect this to be enforced at compile time? Should it cause a crash at runtime or just do nothing?
If you expect it to happen at compile time, I'm pretty sure it's not possible for a compiler to catch something like that.
If you want some other behavior, you can make it a private variable with a public set method that does whatever you want if it's been already set.
Or you could encapsulate it in an instance of a custom class that does whatever behavior you want.
Solution 3:[3]
You can use following delegate class:
import kotlin.reflect.KProperty
class WriteOnce<T> {
private var holder = holdValue<T>()
private var value by holder
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
if (!holder.hasValue) {
throw IllegalStateException("Property must be initialized before use")
}
return value
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
if (holder.hasValue) {
throw RuntimeException("Write-once property already has a value")
}
this.value = value
}
}
fun <T> holdValue() = ValueHolder<T>()
class ValueHolder<T> {
var value: T? = null
var hasValue: Boolean = false
private set
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value = value
hasValue = true
}
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
return this.value!!
}
}
Usage:
var example by WriteOnce<String>()
If you try to write a to the variable a second time it will produce a RuntimeException:
java.lang.RuntimeException: Write-once property already has a value
Not having any value also produces an exception, similar to as if you were using lateinit:
java.lang.IllegalStateException: Property must be initialized before use
Which means this is val and lateinit combined because you can set the value at any time, only once ever.
The downside to this implementation is that this is not checked at compile time, meaning that you will only ever see errors in runtime. If that's acceptable in your use case, it certainly would be a good solution for what you're looking for.
For me this is more of a way to make sure that a variable is only every assigned once by code I control – something I can catch during testing as well as in production as a way to improve security by preventing foreign code from changing a variable.
Solution 4:[4]
It is possible you can use You can create a custom delegate for the property that is a combination of the existing notNull delegate and your own idea of set once. Read more about property delegates for information on how to create a custom one that can do whatever you want, including the use case you want here. You would then not use lateinit but instead this delegate.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Makoto |
| Solution 2 | EmilioPelaez |
| Solution 3 | xdevs23 |
| Solution 4 | Jayson Minard |
