如何使Kotlin在非主构造函数中也使用字段而不是setter



如果使用主构造函数初始化属性,则不会调用其setter:

open class Foo(open var bar: Any)
open class Baz(bar: Any) : Foo(bar) {
override var bar: Any
get() = super.bar
set(_) = throw UnsupportedOperationException()
}
...
Baz(1) // works, no setter is called

这也起作用:

open class Foo(bar: Any) {
open var bar: Any = bar // no exception in Baz(1)
}

但事实并非如此:

open class Foo {
open var bar: Any
constructor(bar: Any) {
this.bar = bar // UnsupportedOperationException in Baz(1)
}
}

这甚至无法编译:

open class Foo(bar: Any) {
open var bar: Any // Property must be initialized or be abstract
init {
this.bar = bar
}
}

我这么问是因为我需要制作barlateinit才能做到这一点:

Baz(1) // works
Foo(2) // this too
Foo().bar = 3 // should work too

但这在科特林不起作用:

// 'lateinit' modifier is not allowed on primary constructor parameters
open class Foo(open lateinit var bar: Any)

没有这样的语法:

open class Foo() {
open lateinit var bar: Any
constructor(bar: Any) {
// instead of this.bar
fields.bar = bar // or whatever to bypass the setter
}
}

有什么办法解决这个问题吗?(除了反射,添加额外的属性和委托(

要允许Foo()对象的存在,您需要为bar属性提供一些init值(或使其成为lateinit(。但是open lateinit属性无法在构造函数/init块中初始化,因为这会导致构造函数中的"this"泄漏。

因此有两种选择:

  1. 使用lateinit修饰符并为Foo提供工厂函数而不是二级构造函数(还需要修改Baz类(:
open class Foo {
open lateinit var bar: Any
}
fun Foo(bar: Any): Foo = Foo().also { it.bar = bar }
open class Baz(bar: Any) : Foo() {
init {
super.bar = bar
}
override var bar: Any
get() = super.bar
set(_) = throw UnsupportedOperationException()
}
  1. bar属性使用一些伪默认值,并添加自定义getter以模拟lateinit属性行为:
open class Foo(bar: Any = NONE) {
companion object {
private object NONE
}
open var bar = bar
get() = field.takeIf { it != NONE } ?: throw UninitializedPropertyAccessException()
}

然而,请注意,允许在基类中设置属性,但在派生类中不允许它,这是一个糟糕的类设计,因为它违反了Liskov替换原则。

最新更新