[Kotlin] Kotlin constructor, member variables, init code block execution order

In Kotlin, we often see primary constructors, member variables, and init code blocks (also called initializers). What is their execution timing and order? Take a look at the official example:

class InitOrderDemo(name: String) {
    
    
    val firstProperty = "First property: $name".also(::println)
    
    init {
    
    
        println("First initializer block that prints ${
      
      name}")
    }
    
    val secondProperty = "Second property: ${
      
      name.length}".also(::println)
    
    init {
    
    
        println("Second initializer block that prints ${
      
      name.length}")
    }
}

The official example only shows the print results without in-depth analysis.

Let's analyze the following questions.

Why member variables can directly use the parameters of the constructor?
What is the order in which member variables and init initializers are executed?
What is the order in which constructors and init initializers are executed?

The above code calls InitOrderDemo("hello"), and the printed results are as follows:

First property: hello
First initializer block that prints hello
Second property: 5
Second initializer block that prints 5

You can see that the execution order is executed in the order in which they are declared.

So why is this happening? Converting this code into Java code will be clear at a glance.

Steps to convert kotlin to Java code:
open a Kotlin class -> click tools on the menu bar of android studio -> kotlin -> Decompile kotlin to java

The conversion into Java code is as follows:


import kotlin.Metadata;
import kotlin.Unit;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

public final class InitOrderDemo {
    
    
   @NotNull
   private final String firstProperty;
   @NotNull
   private final String secondProperty;

   @NotNull
   public final String getFirstProperty() {
    
    
      return this.firstProperty;
   }

   @NotNull
   public final String getSecondProperty() {
    
    
      return this.secondProperty;
   }

   public InitOrderDemo(@NotNull String name) {
    
    
      Intrinsics.checkNotNullParameter(name, "name");
      super();
      String var2 = "First property: " + name;
      boolean var3 = false;
      boolean var4 = false;
      int var6 = false;
      boolean var7 = false;
      System.out.println(var2);
      Unit var9 = Unit.INSTANCE;
      this.firstProperty = var2;
      var2 = "First initializer block that prints " + name;
      var3 = false;
      System.out.println(var2);
      var2 = "Second property: " + name.length();
      var3 = false;
      var4 = false;
      var6 = false;
      var7 = false;
      System.out.println(var2);
      var9 = Unit.INSTANCE;
      this.secondProperty = var2;
      var2 = "Second initializer block that prints " + name.length();
      var3 = false;
      System.out.println(var2);
   }
}

It can be seen that Kotlin's member variable initialization is placed in the constructor, and the init code block is also "copied" into the constructor, and "copied" in the order of declaration, so they are all part of the constructor. So kotlin is still different from Java. Java member variables are initialized prior to constructors, and Kotlin is initialized in the order of declaration.

And you can see that the read-only variable declared by Kotlin with val just adds a final keyword in Java, and the get method is generated without the set method.

Let’s slightly modify the above kotlin code, add default values ​​to the main constructor parameters to see what happens, change val to var variables to see the difference, and add a secondary constructor to see the code execution order:

class InitOrderDemo(name:String="test") {
    
    
    val firstProperty = "First property: $name".also(::println)

    init {
    
    
        println("First initializer block that prints ${
      
      name}")
    }

    var secondProperty = "Second property: ${
      
      name.length}".also(::println)

    init {
    
    
        println("Second initializer block that prints ${
      
      name.length}")
    }

    constructor(name:String,age:Int):this(name){
    
    
        println("Sub constructor block that prints age= ${
      
      age}")
    }
}

The conversion into Java code is as follows:

public final class InitOrderDemo {
    
    
   @NotNull
   private final String firstProperty;
   @NotNull
   private String secondProperty;

   @NotNull
   public final String getFirstProperty() {
    
    
      return this.firstProperty;
   }

   @NotNull
   public final String getSecondProperty() {
    
    
      return this.secondProperty;
   }

   public final void setSecondProperty(@NotNull String var1) {
    
    
      Intrinsics.checkNotNullParameter(var1, "<set-?>");
      this.secondProperty = var1;
   }

   public InitOrderDemo(@NotNull String name) {
    
    
      Intrinsics.checkNotNullParameter(name, "name");
      super();
      String var2 = "First property: " + name;
      boolean var3 = false;
      boolean var4 = false;
      int var6 = false;
      boolean var7 = false;
      System.out.println(var2);
      Unit var9 = Unit.INSTANCE;
      this.firstProperty = var2;
      var2 = "First initializer block that prints " + name;
      var3 = false;
      System.out.println(var2);
      var2 = "Second property: " + name.length();
      var3 = false;
      var4 = false;
      var6 = false;
      var7 = false;
      System.out.println(var2);
      var9 = Unit.INSTANCE;
      this.secondProperty = var2;
      var2 = "Second initializer block that prints " + name.length();
      var3 = false;
      System.out.println(var2);
   }

   // $FF: synthetic method
   public InitOrderDemo(String var1, int var2, DefaultConstructorMarker var3) {
    
    
      if ((var2 & 1) != 0) {
    
    
         var1 = "test";
      }

      this(var1);
   }

   public InitOrderDemo() {
    
    
      this((String)null, 1, (DefaultConstructorMarker)null);
   }

   public InitOrderDemo(@NotNull String name, int age) {
    
    
      Intrinsics.checkNotNullParameter(name, "name");
      this(name);
      String var3 = "Sub constructor block that prints age= " + age;
      boolean var4 = false;
      System.out.println(var3);
   }
}

As you can see, three constructors are generated, one with name parameter, one with name and age, and one without parameter (with default value). In the no-argument constructor, the default value is delegated to the argument constructor. Variables declared as var generate both get and set functions. The code in the secondary constructor has the lowest optimization level and is executed last.

in conclusion:

1. No matter which secondary constructor is called, the main constructor (initializing member variables and executing the init code block) is executed first, and then the code of the secondary constructor is executed. So kotlin stipulates that the secondary constructor must first delegate to the primary constructor.

2. The member variable initialization and the init code block are executed first according to the order of their declaration.

From the above analysis, I believe everyone has a clear understanding of the execution order and principles of kotlin's constructors, member variables, and init code blocks.

The author also analyzes their execution order
from the perspective of bytecode: analyze the execution order of kotlin constructor, member variables, and init code blocks from the perspective of bytecode

Guess you like

Origin blog.csdn.net/devnn/article/details/121991390