Kotlin Under the Hood: Exploring Constructors and Init Blocks

Abhaysing Bhosale on 2024-10-15

Kotlin Under the Hood: Exploring Constructors and Init Blocks

Hello! Have you ever wondered how Kotlin’s constructors and init blocks work under the hood? In this blog, we’ll dive into it.

Before we dive into the details, let’s first understand what constructors and init blocks are. Kotlin has two main types of constructors: the Primary Constructor and Secondary Constructors.

Primary Constructor

The primary constructor is defined in the class header, directly after the class name, and is part of the class declaration itself. It has the following features:

Secondary Constructor

A secondary constructor which also known as an auxiliary constructor is an additional constructor you can define within the class body using the constructor keyword. It allows you to provide alternative ways to instantiate an object when the primary constructor doesn’t meet your specific requirements.

Here are some key points about secondary constructors:

In this example:

Init Block

The init block is used for initializing properties or executing startup logic right after an object is created. It runs after the primary constructor and before any secondary constructors.

Key Characteristics:

Decoding Kotlin Constructors and Init Blocks

Now, let’s dive into how it works. First, to gain deeper insights, we can use IntelliJ IDEA’s decompilation feature. By navigating to Tools -> Kotlin -> Kotlin Bytecode and selecting Decompile, we can view the underlying Java code generated from our Kotlin constructs.

Now, let’s check what happens when we create a primary constructor in Kotlin. Here’s our code:

class User(val name: String, val age: Int) {}

Now, let’s take a look at the underlying Java code generated from this Kotlin class:

public final class User {
   @NotNull
   private final String name;
   private final int age;

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final int getAge() {
      return this.age;
   }

   public User(@NotNull String name, int age) {
      super();
      this.name = name;
      this.age = age;
   }
}

I’ve simplified it by removing assertions and other metadata for clarity.

So, what do we see here?

class User(val name: String, val age: Int) {
    init {
        println("This is the first init block. Name: $name & Age: $age")
    }

    init {
        println("This is the second init block.")
    }
}

Here’s the underlying Java code:

public final class User {
   @NotNull
   private final String name;
   private final int age;

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final int getAge() {
      return this.age;
   }

   public User(@NotNull String name, int age) {
      super();
      this.name = name;
      this.age = age;
      String var3 = "This is the first init block. Name: " + this.name + " & Age: " + this.age;
      System.out.println(var3);
      var3 = "This is the second init block.";
      System.out.println(var3);
   }
}

So, what do we observe here?

This is the first init block. Name: Abhay & Age: 22
This is the second init block.

Let’s explore the secondary constructor.

class User(val name: String, val age: Int){
    init {
        println("This is the first init block. Name: $name & Age: $age")
    }
    // secondary constructor
    constructor(name: String): this(name, 22){
        println("This is the secondary constructor: Name: $name & Age: $age")
    }
    init {
        println("This is the second init block.")
    }
}

Here’s the underlying Java code:

public final class User {
   @NotNull
   private final String name;
   private final int age;

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final int getAge() {
      return this.age;
   }

   public User(@NotNull String name, int age) {
      super();
      this.name = name;
      this.age = age;
      String var3 = "This is the first init block. Name: " + this.name + " & Age: " + this.age;
      System.out.println(var3);
      var3 = "This is the second init block.";
      System.out.println(var3);
   }

   public User(@NotNull String name) {
      this(name, 22);
      String var2 = "This is the secondary constructor: Name: " + name + " & Age: " + this.age;
      System.out.println(var2);
   }
}
fun main() {
    // Calling the secondary constructor
    User("Abhay")
}

You’ll get the output:

This is the first init block. Name: Abhay & Age: 22
This is the second init block.
This is the secondary constructor: Name: Abhay & Age: 22

Here, you can see that all the init blocks are executed first, followed by the secondary constructor.

Thanks for reading this blog! 😊 If you want to explore more “under the hood” insights and deep dives into Kotlin, be sure to follow me for future updates and posts.

Feel free to connect with me on: