在 Kotlin 中,我们有
val
,它是最终的且无法更改。例如
val something = "Something"
如果稍后初始化某个值,我们使用
lateinit var
。
lateinit var something: String
但这是
var
而不是val
。我想设置一次(不在构造函数中),并将其作为最终的。我怎样才能实现这个目标?
阅读 Kotlin 的约定,一个最终的后期初始化变量是不可能的。
考虑其用例:
在处理可能尚未初始化的变量时,通常,声明为非空类型的属性必须在构造函数中初始化。然而,这通常并不方便。例如,可以通过依赖项注入或在单元测试的设置方法中初始化属性。在这种情况下,您不能在构造函数中提供非 null 初始值设定项,但您仍然希望在引用类体内的属性时避免 null 检查。
lateinit var
提供了相对的理智,例如注入字段的情况(如 Spring 和 @Autowired
)。 然后,严格来说,在依赖注入的上下文中,如果您没有有办法在编译时具体实例化变量,那么您不能将其保留为最终字段。
从 Java 到 Kotlin 世界,将后期初始化的变量作为 Final 引入看起来就像 Spring 中的这样自相矛盾:
@Autowired
private final Interface something;
当您尝试再次设置它时,您认为行为应该是什么?您希望在编译时强制执行此操作吗?它应该在运行时导致崩溃还是什么也不做?
如果您期望它在编译时发生,我很确定编译器不可能捕获类似的东西。
如果您想要一些其他行为,您可以将其设为具有公共 set 方法的私有变量,如果已经设置了该方法,该方法可以执行您想要的任何操作。
或者您可以将其封装在自定义类的实例中,该类可以执行您想要的任何行为。
您可以使用以下委托类:
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!!
}
}
用途:
var example by WriteOnce<String>()
如果您尝试第二次向变量写入 a ,则会产生 RuntimeException:
java.lang.RuntimeException: Write-once property already has a value
没有任何值也会产生异常,类似于您使用
lateinit
:
java.lang.IllegalStateException: Property must be initialized before use
这意味着这是
val
和 lateinit
的组合,因为您可以随时设置该值,但只能设置一次。
此实现的缺点是在编译时不会检查,这意味着您只会在运行时看到错误。如果这在您的用例中是可以接受的,那么它肯定会是您正在寻找的一个很好的解决方案。
对我来说,这更像是一种确保变量仅由我控制的代码分配一次的方法——我可以在测试和生产过程中捕捉到这一点,作为通过防止外部代码更改变量来提高安全性的一种方法.
使用委托属性
fun <T : Any> Delegates.once(): ReadWriteProperty<Any?, T> = object : ReadWriteProperty<Any?, T> {
private var value: T? = null
public override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return value ?: throw IllegalStateException("Property ${property.name} should be initialized before get.")
}
public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
if (this.value != null) throw IllegalStateException("Property ${property.name} cannot be set more than once.")
this.value = value
}
}
用途:
var value:Int by Delegates.once()
您可以使用您可以为该属性创建一个自定义委托,该属性是现有
notNull
委托和您自己的 set once
想法的组合。 阅读有关“属性委托”的更多信息,了解如何创建可以执行您想要的任何操作的自定义属性委托,包括您想要的用例。 然后您就不会使用 lateinit
而是使用这个委托。val
使用
by lazy
进行初始化,并从 lateinit
读取内容,同时保持 lateinit
字段私有:val something by lazy(LazyThreadSafetyMode.NONE) {
_something
}
private lateinit var _something: String
这不会让您免于第二次分配
_something
,但有助于限制范围。