17

I have created the following class hierarchy:

open class A {
    init {
        f()
    }

    open fun f() {
        println("In A f")
    }
}

class B : A() {
    var x: Int = 33

    init {
        println("x: " + x)
    }

    override fun f() {
        x = 1
        println("x in f: "+ x)
    }

    init {
        println("x2: " + x)
    }
}

fun main() {
    println("Hello World!!")
    val b = B()
    println("in main x : " + b.x)
}

The output of this code is

Hello World!!
x in f: 1
x: 33
x2: 33
in main x : 33

But if I change the initialization of x from

var x: Int = 33

to

var x: Int = 0

the output shows the invocation of the method in contrast to the output above:

Hello World!!
x in f: 1
x: 1
x2: 1
in main x : 1

Does anyone know why the initialization with 0 causes a different behaviour than the one with another value?

deHaar
  • 17,687
  • 10
  • 38
  • 51
  • 4
    Not directly related, but calling overridable methods from constructors is generally not a good practice since it may lead to unexpected behavior (and effectively breaking the superclass contract / invariants from subclasses). – Adam Hošek Jan 20 '20 at 12:45

2 Answers2

19

super class is initialized before sub class.

The constructor call of B calls the constructor of A, which calls the function f printing "x in f: 1", after A is initialized, the rest of B is initialized.

So essentially, the setting of the value is being overwritten.

(When you initialize primitives with their zero value in Kotlin, they technically just dont initialize at all)

You can observe this "overwrite" behavior by changing the signature from

var x: Int = 0 to var x: Int? = 0

Since x is no longer the primitive int, the field actually gets initialized to a value, producing the output:

Hello World!!
x in f: 1
x: 0
x2: 0
in main x : 0
Sxtanna
  • 211
  • 2
  • 8
  • 5
    *When you initialize primitives with their zero value in Kotlin, they technically just dont initialize at all* is what I wanted to read... Thanks! – deHaar Jan 20 '20 at 11:56
  • This still seems like a bug/inconsistency. – Kroppeb Jan 20 '20 at 12:22
  • 2
    @Kroppeb this is just Java, the same behaviour can be observed in Java code alone. It has nothing to do with Kotlin – Sxtanna Jan 20 '20 at 12:46
9

This behavior is described in the documentation — https://kotlinlang.org/docs/reference/classes.html#derived-class-initialization-order

If any of those properties are used in the base class initialization logic (either directly or indirectly, through another overridden open member implementation), it may lead to incorrect behavior or a runtime failure. When designing a base class, you should therefore avoid using open members in the constructors, property initializers, and init blocks.

UPD:

There is a bug that produces this inconsistency — https://youtrack.jetbrains.com/issue/KT-15642

When a property is assigned as a side-effect of a virtual function call inside the super constructor, its initializer does not overwrite the property if the initializer expression is the type default value (null, primitive zero).

vanyochek
  • 815
  • 4
  • 10
  • 1
    Furthermore, IntelliJ warns you about it. Calling `f()` in the `init` block of `A` gives the warning "Calling non-final function f in constructor" – Kroppeb Jan 20 '20 at 13:30
  • In the documentation you provided, it says *"the base class initialization is done as the first step and thus happens before the initialization logic of the derived class is run"* which is exactly what happens in the first example in the question. However in the second example the initialization instruction (`var x: Int = 0`) of the derived class is not run at all which is contrary to what the documentation says which leads me to believe that this might be a bug. – Subaru Tashiro Jan 20 '20 at 15:46
  • @SubaruTashiro Yes, you're right. It's another issue — https://youtrack.jetbrains.com/issue/KT-15642. – vanyochek Jan 21 '20 at 07:26