Java constructor is just a method that run after object creation. Before running the constructor, all the class fields get initialized.
In Kotlin there are two types of constructors namely primary constructor and the secondary constructor. I see primary constructor as a regular java constructor that supports field encapsulation built-in. After compilation, primary constructor fields are put on the top of the class if they have declared visible to the whole class.
In java or kotlin, constructor is invoked after initializing class fields. But in primary constructor we cannot write any statements. If we want to write statements that need to be executed after object creation, we have to put them in the initialization blocks. But init blocks are executed as they appear in the class body. We can define multiple init blocks in the class. They will be executed from top to the bottom.
Lets do some experiment with init blocks..
Test.kt
fun main() {
Subject("a1")
}
class Element {
init {
println("Element init block 1")
}
constructor(message: String) {
println(message)
}
init {
println("Element init block 2")
}
}
class Subject(private val name: String, e: Element = Element("$name: first element")) {
private val field1: Int = 1
init {
println("$name: first init")
}
val e2 = Element("$name: second element")
init {
println("$name: second init")
}
val e3 = Element("$name: third element")
}
Lets compile the above and run it.
kotlinc Test.kt -include-runtime -d Test.jar
java -jar Test.jar
The output of the above program is
Element init block 1
Element init block 2
a1: first element
a1: first init
Element init block 1
Element init block 2
a1: second element
a1: second init
Element init block 1
Element init block 2
a1: third element
As you can see, first primary constructor was called, before secondary constructor, all the init blocks were executed. This is because init blocks become a part of the constructor in the order they appear in the class body.
Lets compile the kotlin code to java byte code and decompile it back to java. I used jd-gui
to decompile java classes. You can install it with yay -S jd-gui-bin
in arch linux based distributions.
Here is the output I got after decompiling Subject.class
file
import kotlin.Metadata;
import kotlin.jvm.internal.DefaultConstructorMarker;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
@Metadata(mv = {1, 6, 0}, k = 1, xi = 48, d1 = {"\000\034\n\002\030\002\n\002\020\000\n\000\n\002\020\016\n\000\n\002\030\002\n\002\b\007\n\002\020\b\030\0002\0020\001B\027\022\006\020\002\032\0020\003\022\b\b\002\020\004\032\0020\005\006\002\020\006R\021\020\007\032\0020\005\006\b\n\000\032\004\b\b\020\tR\021\020\n\032\0020\005\006\b\n\000\032\004\b\013\020\tR\016\020\f\032\0020\rX\006\002\n\000R\016\020\002\032\0020\003X\004\006\002\n\000"}, d2 = {"LSubject;", "", "name", "", "e", "LElement;", "(Ljava/lang/String;LElement;)V", "e2", "getE2", "()LElement;", "e3", "getE3", "field1", ""})
public final class Subject {
@NotNull
private final String name;
private final int field1;
@NotNull
private final Element e2;
@NotNull
private final Element e3;
public Subject(@NotNull String name, @NotNull Element e) {
this.name = name;
this.field1 = 1;
System.out
.println(Intrinsics.stringPlus(this.name, ": first init"));
this.e2 = new Element(Intrinsics.stringPlus(this.name, ": second element"));
System.out
.println(Intrinsics.stringPlus(this.name, ": second init"));
this.e3 = new Element(Intrinsics.stringPlus(this.name, ": third element"));
}
@NotNull
public final Element getE2() {
return this.e2;
}
@NotNull
public final Element getE3() {
return this.e3;
}
}
As you can see all the init blocks have become a part of the constructor in the order they appear in the class body. I noticed one thing different from java. Class fields were initialized in the constructor. Class fields and init blocks were initialized in the order they appear in the class body. It seems order is so important in kotlin.
class Example(val name: String = "default name")
– Slicer