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:
- You can use
val
for read-only properties andvar
for mutable properties. - No need to use the
constructor
keyword, unlike in other languages. - It’s implicitly called when an instance of the class is created.
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:
- Defined within the class using the
constructor
keyword. - Can call the primary constructor using
this(...)
. - Useful for providing different ways to create instances of a class.
- Each secondary constructor can delegate to the primary constructor or to another secondary constructor.
- There can be multiple secondary constructors.
In this example:
- The primary constructor takes both
name
andage
. - The secondary constructor allows you to create a
User
object by passing only aname
. It automatically assigns a defaultage
of 22. this(name, 22)
is a constructor delegation.
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:
- The
init
block is called automatically when an instance of the class is created. - You can have multiple
init
blocks, and they will execute in the order they are defined within the class. - Any properties initialized in the primary constructor can be accessed and used inside the
init
block.
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?
- The
User
class is final because Kotlin classes are final by default. - The primary constructor is translated into a Java constructor.
getName()
andgetAge()
methods provide access to these properties.- The
@NotNull
annotation indicates thatname
cannot be null, ensuring safety. - There are no setter methods since the properties are defined as immutable in the constructor.
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?
- The code from the
init
blocks is added to the primary constructor and executes after the properties in the constructor are initialized. - The
init
blocks execute in the order they appear, right after the primary constructor and before any secondary constructors.
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); } }
- A secondary constructor in Kotlin overloads the constructor in Java.
- The secondary constructor can call the primary constructor or another secondary constructor using
this()
. - The secondary constructor executes after all
init
blocks have been processed. - In this example, when you create an instance of
User
using the secondary constructor with just a name, it initializes theage
property to a default value of 22.
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:
- Twitter: https://twitter.com/abhaycloud_dev
- LinkedIn: https://linkedin.com/in/abhaysing-bhosale
- GitHub: Abhay-cloud (Abhay) (github.com)