Home > Back-end >  Kotlin property initialization
Kotlin property initialization

Time:03-16

I've created the following snippet, not anything that I would actually write in production, but just to help me understand how properties are initialized:

class C {
    val a: String = run {
        println("init a")
        f()
    }
    val b: String = run {
        println("init b: a=$a")
        a
    }

    private fun f(): String {
        println("f(): a=$a, b=$b")
        return b
    }
}

fun main() {
    println(C().a)
    println(C().b)
}

Note that all properties are val and String, so immutable and non-nullable. Yet the output shows that they are null.

Is the result of this code well-defined? Is there somewhere in the language spec that says that a non-nullable property can in fact be null when you access it from the wrong place?

CodePudding user response:

According to the Kotlin/Core spec, the order of the execution is specified,

When a classifier type is initialized using a particular secondary constructor ctor delegated to primary constructor pctor which, in turn, is delegated to the corresponding superclass constructor sctor , the following happens, in this initialization order:

  • [...]
  • Each property initialization code as well as the initialization blocks in the class body are invoked in the order of appearance in the class body;
  • [...]

but the values of the uninitialised properties are not.

If any of the properties are accessed before they are initialized w.r.t initialization order (e.g., if a method called in an initialization block accesses a property declared after the initialization block), the value of the property is unspecified. It stays unspecified even after the “proper” initialization is performed.

In other words, you will see the messages printed in the order of

init a
f(): a=null, b=null
init b: a=null
null

but the values are not guaranteed to be null.

There might be stronger guarantees in Kotlin/JVM, but there is no spec for that right now.

CodePudding user response:

This is explicitly not an answer, just two small remarks/questions:

First, doesn't one of the three members a, b, or f() need to be set to a or return a value? The way it is in the OPs code, everything is null by definition as there is no non-null value anywhere. The 'call chain' is a –> f() –> b, so if we set b to a value like "start", it would look like this:

class C {
  val a: String = run { f() }
  val b: String = run { "start" }
  private fun f(): String = b
}

fun main() {
  val c = C()
  println(c.a)   // Output: null
  println(c.b)   // Output: "start"
}

Secondly, if the order is changed so that b is appearing in the code before the other two members we get a different result:

class C {
  val b: String = run { "start" }
  val a: String = run { f() }
  private fun f(): String = b
}

fun main() {
  val c = C()
  println(c.a)   // Output: "start"
  println(c.b)   // Output: "start"
}
  • Related