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

时间:2021-05-31 00:13:06

标签: kotlin

如果我使用主构造函数来初始化一个属性,它的 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
    }
}

我之所以这么问是因为我需要让 bar lateinit 才能做到这一点:

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

但这在 Kotlin 中不起作用:

// '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
    }
}

任何想法如何解决这个问题? (除了反射,添加额外的属性和委托)

1 个答案:

答案 0 :(得分:0)

要允许 Foo() 对象存在,您需要为 bar 属性提供一些初始值(或使其为 lateinit)。但是 open lateinit 属性不能在构造函数/init 块中初始化,因为它导致 leaking 'this' in constructor

所以有两种选择:

  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 substitution principle