What are the Kotlin class initialisation semantics

2019-06-15 02:41发布

问题:

I haven't been able to find anything in the language definition that explains the initialisation of a class in Kotlin.

import java.util.Properties

fun main(args: Array<String>) {
    val out = MyClass()
    out.fn()
}

class MyClass {
    private val a = Properties() // 1

    init {
        fn()
    }

    public fun fn() {
        println("Fn called.  a = $a")
    }

    //  private val a = Properties() // 2
}

The results of running this program change depending whether the property is initialised at (1) or at (2).

I'm surprised that the declaration order is relevant in the language and would like to understand the decisions behind this. My expectation would be that properties are initialised before the constructor body is invoked.

回答1:

My expectation would be that properties are initialised before the constructor body is invoked.

Well, init block is not a constructor. It is a different construct which allows you to perform the initialization of the object and they [init blocks] are performed in the declaration order with the property initializers.

Constructors are a different beast ant they are performed after all the properties were initialized and all init blocks were performed. Look at the following example:

class A(val value: Int) {
    constructor(): this(0) {        
        println("Constructor")
    }    

    init {
        println("Init block")
    }
}

fun main(args: Array<String>) {
    val a = A()
}

Output is:

Init block  
Constructor

You can place the init block wherever you want: before the constructor or after it; it will always be performed before the A's constructor (secondary constructor, in this example).



回答2:

Simply put: when an instance of a class is created, (almost) firstly runs the constructor of the parent class (if present), then the primary constructor.

The primary constructor executes code declared in the class body from the top to the bottom. Also the names became available by the same rule:

class Foo(a: String = "might be first"
          val b: String = "second" + a) : Boo(a + b + "third"){
    var c = a + "fourth" + b

    init {print("fifth: $c")}

    val d = "sixth"
    init {print("seventh: the end of the primary constructor"}
}

If you invoke a secondary constructor, then it works after the primary one as it is composed in the chain (similar to invoking the parent constructors).