0

If I use a primary constructor to initialize a property, its setter won't be called:

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

This works too:

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

But this doesn't:

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

This can't even be compiled:

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

I'm asking because I need to make bar lateinit to be able doing this:

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

But this doesn't work in Kotlin:

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

And there's no syntax like this:

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

Any ideas how to solve this? (besides reflection, adding extra properties and delegates)

Viktor
  • 1,298
  • 15
  • 28
  • Generally, you want all of your constructors to simply delegate to the primary one, which has all information necessary to construct every field. If you find that your secondary constructors are trying to bypass setters, you might consider making *that* your primary constructor and turning the former primary into a secondary constructor or a factory method. – Silvio Mayolo May 31 '21 at 00:25
  • So your main goal is to make possible to set `bar` for `Foo` both through constructor or property, but disallow property access when dealing with `Baz`? – broot May 31 '21 at 00:33

1 Answers1

0

To allow existence of Foo() object you need to provide some init value for bar property (or make it lateinit). But open lateinit property can't be initialized in constructor/init block, because it leads to leaking 'this' in constructor.

So there are 2 options:

  1. Use lateinit modifier and provide factory function instead of secondary constructor for Foo (also requires modification of Baz class):
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. Use some dummy default value for bar property and add custom getter to emulate lateinit property behavior:
open class Foo(bar: Any = NONE) {
    companion object {
        private object NONE
    }
    open var bar = bar
        get() = field.takeIf { it != NONE } ?: throw UninitializedPropertyAccessException()
}

However note, that making possible to set property in the base class, but disallowing it in the derived is a poor class design, because it breaks Liskov substitution principle.