Why does this fix the "accessing non-final property in constructor" warning?


#1

So I had some code that looked like this:

abstract class GenericField {
    abstract val id: Int
    abstract val data: ByteBuffer
}

open class OneByteFieldType : GenericField {
    override val id: Int
    override val data: ByteBuffer

    constructor(buf: ByteBuffer) {
        this.id = parseId(buf)
        this.data = parseData(buf)
    }

    constructor(id: Int, data: ByteBuffer) {
        this.id = id
        this.data = data
    }
}

And it warned me about “accessing non-final property in constructor” when I assign this.id and this.data in both the constructors, but, if I change the code to this:

abstract class GenericField {
    abstract val id: Int
    abstract val data: ByteBuffer
}

open class OneByteFieldType(
    override val id: Int,
    override val data: ByteBuffer
) : GenericField() {

    constructor(buf: ByteBuffer) : this(parseId(buf), parseData(buf))
}

The warning is gone. But why? What about a primary constructor makes this problem go away? From what I can tell, it can still happen. Take this example:

abstract class Parent {
    abstract val id: Int
}

open class Child1(
    override var id: Int
) : Parent()

class Child2 : Child1 {
    override var id: Int = 0

    constructor(id: Int) : super(id) {
//        this.id = id
    }

    fun parentId(): Int = super.id
}

fun main(args: Array<String>) {
    val c2 = Child2(42)
    println(c2.id)
    println(c2.parentId())
// Prints:
// 0
// 42
}

When Child2 calls super, its id field isn’t initialized yet so it uses the one in Child1. Isn’t this the issue that the original warning (“accessing non-final property in constructor”) was alerting me about? Can the compiler just not tell that the same issue will happen here? Or is something actually different?


#2

The issue is to do with a possible subclass of OneByteFieldType. In the primary constructor case you are initialising a property (the field directly). However it may possible that the subclass overrides the id val again. This subclass.id is not initialised yet when the parent constructor is evaluated as evaluation constructs parents before children. The assignment in the constructor body would however just invoke the setter on that property causing the access (even for writing) of that uninitialised property. If you were to make OneByteFieldType not open, or made id and data final this would be resolved.


#3

Thanks…I get that there’s an issue with accessing an open member in an open class: setting the value there will not be reflected in the child if it’s overridden there as well. My question is that: this same issue can happen when using a primary constructor there as well, so why does the compiler still not warn about it? Isn’t that issue what the warning is trying to tell the user?


#4

The thing is that you are not setting a value, you are calling a setter that may have arbitrary code in it while the object is only partially initialised (the actual child is not initialised yet)