SoFunction
Updated on 2025-03-09

Kotlin bytecode layer explores the execution order of constructors, member variables and init code blocks

I wrote an article before, analyzing the execution order of Kotlin constructor, member variable initialization, and init code block from the perspective of Java syntax:

Detailed explanation of the execution order of Kotlin constructor, member variables and init code blocks

This time, they will analyze their execution order from the perspective of bytecode.

Let's use the previous example:

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

CallInitOrderDemo(“hello”)The printing 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 they are declared.

After converting the above Koltin code into bytecode, the content is displayed as follows:

// ================com/devnn/javalib/ =================
// class version 52.0 (52)
// access flags 0x31
public final class com/devnn/javalib/InitOrderDemo {
  // access flags 0x12
  private final Ljava/lang/String; firstProperty
  @Lorg/jetbrains/annotations/NotNull;() // invisible
  // access flags 0x11
  public final getFirstProperty()Ljava/lang/String;
  @Lorg/jetbrains/annotations/NotNull;() // invisible
   L0
    LINENUMBER 4 L0
    ALOAD 0
    GETFIELD com/devnn/javalib/ : Ljava/lang/String;
    ARETURN
   L1
    LOCALVARIABLE this Lcom/devnn/javalib/InitOrderDemo; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1
  // access flags 0x12
  private final Ljava/lang/String; secondProperty
  @Lorg/jetbrains/annotations/NotNull;() // invisible
  // access flags 0x11
  public final getSecondProperty()Ljava/lang/String;
  @Lorg/jetbrains/annotations/NotNull;() // invisible
   L0
    LINENUMBER 10 L0
    ALOAD 0
    GETFIELD com/devnn/javalib/ : Ljava/lang/String;
    ARETURN
   L1
    LOCALVARIABLE this Lcom/devnn/javalib/InitOrderDemo; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1
  // access flags 0x1
  public <init>(Ljava/lang/String;)V
    // annotable parameter count: 1 (visible)
    // annotable parameter count: 1 (invisible)
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
   L0
    ALOAD 1
    LDC "name"
    INVOKESTATIC kotlin/jvm/internal/ (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 3 L1
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
   L2
    LINENUMBER 4 L2
    ALOAD 0
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "First property: "
    INVOKEVIRTUAL java/lang/ (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 1
    INVOKEVIRTUAL java/lang/ (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/ ()Ljava/lang/String;
    ASTORE 2
   L3
    ALOAD 2
    ASTORE 3
   L4
    LINENUMBER 17 L4
    ASTORE 5
   L5
    ICONST_0
    ISTORE 4
   L6
    LINENUMBER 4 L6
   L7
    GETSTATIC java/lang/ : Ljava/io/PrintStream;
    ALOAD 3
    INVOKEVIRTUAL java/io/ (Ljava/lang/Object;)V
   L8
   L9
   L10
    GETSTATIC kotlin/ : Lkotlin/Unit;
    ASTORE 6
    ALOAD 5
   L11
    LINENUMBER 4 L11
   L12
    ALOAD 2
   L13
    PUTFIELD com/devnn/javalib/ : Ljava/lang/String;
   L14
    LINENUMBER 6 L14
    NOP
   L15
    LINENUMBER 7 L15
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "First initializer block that prints "
    INVOKEVIRTUAL java/lang/ (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 1
    INVOKEVIRTUAL java/lang/ (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/ ()Ljava/lang/String;
    ASTORE 2
   L16
    GETSTATIC java/lang/ : Ljava/io/PrintStream;
    ALOAD 2
    INVOKEVIRTUAL java/io/ (Ljava/lang/Object;)V
   L17
   L18
    LINENUMBER 8 L18
    NOP
   L19
    LINENUMBER 10 L19
    ALOAD 0
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "Second property: "
    INVOKEVIRTUAL java/lang/ (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 1
    INVOKEVIRTUAL java/lang/ ()I
    INVOKEVIRTUAL java/lang/ (I)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/ ()Ljava/lang/String;
    ASTORE 2
   L20
    ALOAD 2
    ASTORE 3
   L21
    LINENUMBER 17 L21
    ASTORE 5
   L22
    ICONST_0
    ISTORE 4
   L23
    LINENUMBER 10 L23
   L24
    GETSTATIC java/lang/ : Ljava/io/PrintStream;
    ALOAD 3
    INVOKEVIRTUAL java/io/ (Ljava/lang/Object;)V
   L25
   L26
   L27
    GETSTATIC kotlin/ : Lkotlin/Unit;
    ASTORE 6
    ALOAD 5
   L28
    LINENUMBER 10 L28
   L29
    ALOAD 2
   L30
    PUTFIELD com/devnn/javalib/ : Ljava/lang/String;
   L31
    LINENUMBER 12 L31
    NOP
   L32
    LINENUMBER 13 L32
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "Second initializer block that prints "
    INVOKEVIRTUAL java/lang/ (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 1
    INVOKEVIRTUAL java/lang/ ()I
    INVOKEVIRTUAL java/lang/ (I)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/ ()Ljava/lang/String;
    ASTORE 2
   L33
    GETSTATIC java/lang/ : Ljava/io/PrintStream;
    ALOAD 2
    INVOKEVIRTUAL java/io/ (Ljava/lang/Object;)V
   L34
   L35
    LINENUMBER 14 L35
    RETURN
   L36
    LOCALVARIABLE p1 Ljava/lang/Object; L5 L10 3
    LOCALVARIABLE $i$a$-unknown-InitOrderDemo$firstProperty$1 I L6 L10 4
    LOCALVARIABLE p1 Ljava/lang/Object; L22 L27 3
    LOCALVARIABLE $i$a$-unknown-InitOrderDemo$secondProperty$1 I L23 L27 4
    LOCALVARIABLE this Lcom/devnn/javalib/InitOrderDemo; L0 L36 0
    LOCALVARIABLE name Ljava/lang/String; L0 L36 1
    MAXSTACK = 3
    MAXLOCALS = 7
}

You can see that the constructor, member variable initialization and init code block above are all placed in the bytecode init code block according to the declaration.

The init initializer of bytecode is actually the class constructor. Converting Java code into bytecode also has an init constructor.

Let’s see a Java example below to deepen your understanding of the init initialization block of bytecode.

package ;
public class JavaInit {
    String firstName = "Steven";
    {
        ("This is init block");
    }
    JavaInit(String secondName) {
        ("firstName=" + firstName);
        ("secondName=" + secondName);
    }
    public static void main(String[] args) {
        new JavaInit("Jobs");
    }
}

Run the main function and print the result as follows:

This is init block
firstName=Steven
secondName=Jobs

Put the aboveJavaInitThe content after the class is converted to bytecode is as follows:

// class version 51.0 (51)
// access flags 0x21
public class com/devnn/javalib/JavaInit {
  // compiled from: 
  // access flags 0x0
  Ljava/lang/String; firstName
  // access flags 0x0
  <init>(Ljava/lang/String;)V
   L0
    LINENUMBER 10 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
   L1
    LINENUMBER 4 L1
    ALOAD 0
    LDC "Steven"
    PUTFIELD com/devnn/javalib/ : Ljava/lang/String;
   L2
    LINENUMBER 7 L2
    GETSTATIC java/lang/ : Ljava/io/PrintStream;
    LDC "This is init block"
    INVOKEVIRTUAL java/io/ (Ljava/lang/String;)V
   L3
    LINENUMBER 11 L3
    GETSTATIC java/lang/ : Ljava/io/PrintStream;
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "firstName="
    INVOKEVIRTUAL java/lang/ (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 0
    GETFIELD com/devnn/javalib/ : Ljava/lang/String;
    INVOKEVIRTUAL java/lang/ (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/ ()Ljava/lang/String;
    INVOKEVIRTUAL java/io/ (Ljava/lang/String;)V
   L4
    LINENUMBER 12 L4
    GETSTATIC java/lang/ : Ljava/io/PrintStream;
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "secondName="
    INVOKEVIRTUAL java/lang/ (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 1
    INVOKEVIRTUAL java/lang/ (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/ ()Ljava/lang/String;
    INVOKEVIRTUAL java/io/ (Ljava/lang/String;)V
   L5
    LINENUMBER 13 L5
    RETURN
   L6
    LOCALVARIABLE this Lcom/devnn/javalib/JavaInit; L0 L6 0
    LOCALVARIABLE secondName Ljava/lang/String; L0 L6 1
    MAXSTACK = 3
    MAXLOCALS = 2
  // access flags 0x9
  public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 16 L0
    NEW com/devnn/javalib/JavaInit
    DUP
    LDC "Jobs"
    INVOKESPECIAL com/devnn/javalib/JavaInit.<init> (Ljava/lang/String;)V
    POP
   L1
    LINENUMBER 17 L1
    RETURN
   L2
    LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
    MAXSTACK = 3
    MAXLOCALS = 1
}

It can be seen that the initialization of member variables, constructors, and constructors of Java class are also copied into the init code block. So do they have order issues?

Put the above JavaInit class frname member variable into the initialization block to try:

package ;
public class JavaInit {
    {
        ("This is init block");
    }
    JavaInit(String secondName) {
        ("firstName=" + firstName);
        ("secondName=" + secondName);
    }
    String firstName = "Steven";
    public static void main(String[] args) {
        new JavaInit("Jobs");
    }
}

View bytecode:

// class version 51.0 (51)
// access flags 0x21
public class com/devnn/javalib/JavaInit {
  // compiled from: 
  // access flags 0x0
  Ljava/lang/String; firstName
  // access flags 0x0
  <init>(Ljava/lang/String;)V
   L0
    LINENUMBER 8 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
   L1
    LINENUMBER 5 L1
    GETSTATIC java/lang/ : Ljava/io/PrintStream;
    LDC "This is init block"
    INVOKEVIRTUAL java/io/ (Ljava/lang/String;)V
   L2
    LINENUMBER 13 L2
    ALOAD 0
    LDC "Steven"
    PUTFIELD com/devnn/javalib/ : Ljava/lang/String;
   L3
    LINENUMBER 9 L3
    GETSTATIC java/lang/ : Ljava/io/PrintStream;
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "firstName="
    INVOKEVIRTUAL java/lang/ (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 0
    GETFIELD com/devnn/javalib/ : Ljava/lang/String;
    INVOKEVIRTUAL java/lang/ (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/ ()Ljava/lang/String;
    INVOKEVIRTUAL java/io/ (Ljava/lang/String;)V
   L4
    LINENUMBER 10 L4
    GETSTATIC java/lang/ : Ljava/io/PrintStream;
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "secondName="
    INVOKEVIRTUAL java/lang/ (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 1
    INVOKEVIRTUAL java/lang/ (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/ ()Ljava/lang/String;
    INVOKEVIRTUAL java/io/ (Ljava/lang/String;)V
   L5
    LINENUMBER 11 L5
    RETURN
   L6
    LOCALVARIABLE this Lcom/devnn/javalib/JavaInit; L0 L6 0
    LOCALVARIABLE secondName Ljava/lang/String; L0 L6 1
    MAXSTACK = 3
    MAXLOCALS = 2
  // access flags 0x9
  public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 17 L0
    NEW com/devnn/javalib/JavaInit
    DUP
    LDC "Jobs"
    INVOKESPECIAL com/devnn/javalib/JavaInit.<init> (Ljava/lang/String;)V
    POP
   L1
    LINENUMBER 18 L1
    RETURN
   L2
    LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
    MAXSTACK = 3
    MAXLOCALS = 1
}

It can be seen that the initialization of member variables, constructors, and constructors of Java class are also copied into the bytecode init code block. Java member variable initialization and construction blocks are also executed in the declared order. The difference is that Java constructor code is always placed behind the bytecode init code block.

The init initialization block of bytecode is actually the real constructor of the class. Kotlin multiple init code blocks are copied into the init initialization block of bytecode in order, which can be understood as being part of the constructor.

The initialization of Java and kotlin member variables are placed in the bytecode init code block, that is, executed in the constructor.

This is the article about Kotlin bytecode layer exploring the execution order of constructors, member variables and init code blocks. For more related content of Kotlin constructor, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!