Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

init block position in class in Kotlin

I recently came across a situation where my standard variable's values are replaced by the default one even if I have assigned a value with the constructor using init block.

What I tried was:

class Example(function: Example.() -> Unit) {

    init {
        function()
    }

    var name = "default name"

}


// assigning it like this:
val example = Example { name = "new name" }

// print value
print(example.name)  // prints "default name"

After struggling a bit, I have found that the position of the init block matters. If I put the init block at the last in the class, It initializes the name with default one first and then calls the function() which replaces the value with the "new name".

And If I put it first, it doesn't found the name and it is replaced by the "default name" when properties are initialized.

This is strange to me. Can anyone explain why this has happened?

like image 280
kirtan403 Avatar asked Nov 23 '17 04:11

kirtan403


2 Answers

The reason is kotlin follows top-to-bottom approach

From the documents (An in-depth look at Kotlin’s initializers) Initializers (property initializers and init blocks) are executed in the order that they are defined in the class, top-to-bottom.

You can define multiple secondary constructors, but only one will be called when you create a class instance unless the constructor explicitly calls another one.

Constructors can also have default argument values which are evaluated each time the constructor is called. Like property initializers, these can be function calls or other expressions that will run arbitrary code.

initializers are run top to bottom at the beginning of a class’ primary constructor.

This is correct way

class Example(function: Example.() -> Unit) {
var name = "default name"
init {
    function()
}
}
like image 191
sasikumar Avatar answered Nov 28 '22 15:11

sasikumar


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.

like image 28
UdaraWanasinghe Avatar answered Nov 28 '22 13:11

UdaraWanasinghe