learn the syntax for constructors in Kotlin

Viewed 141

I am learning Kotlin programming language perfectly. I try to write code in different patterns and try to understand. However, I did not understand the thing. Can you help me, please? Here it is:

open class Parent {
    open val foo = 1
    init {
        println(foo)
    }
}
class Child: Parent() {
    override val foo =2
}
fun main() {
    Child()
}

In this code, 0 is the output. How will this be?

3 Answers

This is about the order of construction — and is a subtle gotcha that's easy to fall prey to.  (I'm afraid this answer is a bit long, but the issues here are well worth understanding.)

There are a few basic principles colliding here:

  • Superclass initialisation happens before subclass initialisation.  This includes code in constructors, code in init blocks, and property initialisers: all of that happens for a superclass before any in a subclass.

  • A Kotlin property consists of a getter method, a setter method (if it's a var), and a backing field (if needed).  This is why you can override properties; it means that the accessor method(s) are overridden.

  • All fields initially hold 0/false/null before they get initialised to any other value.  (Normally, you wouldn't get to see that, but this is one of those rare cases.  This differs from languages like C where if you don't explicitly initialise a field it can hold random values depending on what that memory was previously used for.)

From the first principle, when you call the Child() constructor, it will start off by calling the Parent() constructor.  That will set the superclass's foo field to 1, and then get the foo property and print it out.  After that, the Child initialisation happens, which in this case is simply setting its foo field to 2.

The gotcha here is that you effectively have two foos!

Parent defines a property called foo, and that gets accessor methods and a backing field.  But Child defines its own property called foo, overriding the one in Parent — that one overrides the accessor methods, and gets its own backing field as well.

Because of that override, when the Parent's init block refers to foo, it calls the getter method which Child overrides, to get the value of Child's backing field.  And that field hasn't been initialised yet!  So, as mentioned above, it's still holding its initial value of 0, which is the value that the Child getter returns, and hence the value that Parent constructor prints out.

So the real problem here is that you're accessing the subclass field before it's been initialised.  This question shows why that's a really bad idea!  As a general rule:

A constructor/initialiser should never access a method or property that could be overridden by a subclass.

And the IDE helps you out here: if you put your code into IntelliJ, you'll see that the usage of foo is marked with the warning ‘Accessing non-final property foo in constructor’.  That's telling you that this sort of problem is possible.

Of course, there are more subtle cases that an IDE might not be able to warn you about, such as if a constructor calls a non-open method that calls an open one.  So care is needed.

There are occasions when you might need to break that rule — but they're very rare, and you should check very carefully that nothing can go wrong (even if someone comes along later and creates a new subclass).  And you should make it very clear in comments/documentation what's going on and why it's needed.

Now, let's with java understand why. In Java, it's impossible to override fields and under the hood in Kotlin is the same. When you override a property, in fact, you override a getter, not a field. For instance, you can override a property that doesn't have a field with a property that has a field. That's totally legal. However, when both a property from a superclass and an overridden property in a subclass have fields, that might lead to unexpected results. Let's see what bytecode is generated for the Kotlin class in my example. As usual, I'll look at the corresponding Java code instead for simplicity.

public class Parent {
    private final int foo = 1;
    public int getFoo() {return foo;}
    public Parent(){
        System.out.println(getFoo());
    }
}
public final class Child extends Parent {
    private final int foo = 2;
    public int getFoo() {return foo;}
}
public class Main
{
    public static void main (String[] args) {
        new Child();
    }
}

Note two things here. First, the foo get to is trivial, so a field and a getter correspond to the full property. Then because the property is open and can be overridden in a subclass, its usage inside the class is compiled to a getter code, not a field code. Now, the generated code for the child class. Note that the overridden property in the parent class is also compiled to a field and a getter, and now it's another field. What happens when you create an instance of the child class? At first at the parent constructor is called, the parent constructor initializes the first fulfilled with one. But inside the init section, an overridden getter is called which calls get foo from the child class. Because the field in the child class is not yet initialized, 0 is returned. That's why 0 is printed here.

Please go through following points:

  1. Initializer Blocks i.e. init {} block are called during an instance initialization. They are called after Primary Constructor.

  2. In above code println(foo) is placed inside init block.
    Hence, the value which gets printed i.e. 0 in this case, is the value before assignment statement open val foo = 1.

  3. If you want the output to be 1 then make following changes:

open class Parent {
        open var foo : Int = 0

        init {
            foo = 1
            println(foo)
        }
    }

    class Child: Parent() {
        override var foo =2

    }

    fun main() {
        Child()
    }

  1. And lastly, please go through this post. This will help you in getting better understanding of this area.
Related