init block position in class in Kotlin
Asked Answered
H

3

9

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?

Harris answered 23/11, 2017 at 4:58 Comment(4)
Class initialization is top down, init blocks aren't exempted.Aachen
Was this exercise merely to understand Kotlin initialization? If not, you can achieve this with class Example(val name: String = "default name")Slicer
I was trying to do this in my library (github.com/kirtan403/k4kotlin) and just after putting it at the the top, whole functionality broke.Harris
checkout this 👉 init-blocks kotlin vs Java I explained what is init block and how its invoked order of init blocks and global variablesKimble
D
14

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()
}
}
Diathermy answered 23/11, 2017 at 6:13 Comment(2)
Putting init block at the bottom is what makes the sense most of the time? Unless you have some specific needs...Harris
So I guess I just wont be using init then. GJ kotlin.Kamat
G
4

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.

Gerous answered 21/1, 2022 at 7:56 Comment(0)
F
0

As stated in the Kotlin docs:

During an instance initialization, the initializer blocks are executed in the same order as they appear in the class body, interleaved with the property initializers: ...

https://kotlinlang.org/docs/classes.html#constructors

Forewoman answered 27/5, 2021 at 4:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.