ProgrammingKotlin developer

Describe the mechanism of object initialization in Kotlin using init blocks and the 'super' keyword. What are the differences in the initialization order compared to Java, and what nuances exist with respect to inheritance hierarchy and constructor calls?

Pass interviews with Hintsage AI assistant

Answer.

Init blocks and the super call in Kotlin are responsible for the correct initialization of objects considering the class hierarchy. These are fundamental mechanisms that influence constructions and inheritance.

Background

In Java, constructors can be invoked explicitly using super(...) or implicitly by default. In Kotlin, everything is different: there is a clear distinction between primary and secondary constructors, and the init block is used for initialization.

Issue

The main difficulty is to properly understand when properties are initialized, when init blocks are called, and how the order of constructor calls is constructed, especially when inheriting classes and combining primary/secondary constructors.

Solution

  • In Kotlin, all delegations of secondary constructors to the primary are performed first, then the constructor of the super class is called, and only then the init blocks are executed;
  • To pass parameters to super, they must be explicitly specified in the constructor;
  • It is important to remember that properties declared in the class body are initialized before the init block is executed.

Code example:

open class Base(val name: String) { init { println("Base init: $name") } } class Derived(name: String, val age: Int): Base(name) { init { println("Derived init: $name, $age") } } fun main() { Derived("Ivan", 25) } // Output: // Base init: Ivan // Derived init: Ivan, 25

Key features:

  • All properties of the outer class are initialized before the init block is executed;
  • The constructor of the superclass is executed first;
  • The secondary constructor must call the primary either directly or via a chain.

Trick questions.

In what order are init blocks executed in the inheritance chain?

First, the init blocks of the superclass are executed after its properties are initialized, then the init blocks of the subclass after its own properties.

Code example:

open class A { init { println("A") } } class B: A() { init { println("B") } } fun main() { B() } // A -> B

Can you call super() not from a constructor in Kotlin?

No, the superclass call is performed only as part of the constructor declaration providing parameters to the super constructor.

Can the initialization of a secondary constructor not call the primary in Kotlin?

No, all secondary constructors must delegate the call to either another secondary or the primary constructor. The primary, in turn, calls the super constructor.

Common mistakes and anti-patterns

  • Setting a computed property before calling the super constructor will cause an error;
  • Initialization order errors when trying to use properties that have not yet been initialized;
  • Circular dependencies in secondary constructors.

Real-life examples

Negative case

A developer tries to access a property initialized in a subclass within the super class's init block:

open class A { init { println(f()) } open fun f() = "A" } class B : A() { private val s = "B" override fun f() = s }``` **Pros:** - Use of polymorphism. **Cons:** - The property s has not yet been initialized during the super class's init call — the result is null or an error. ## Positive case Separating initialization so as not to call overridden methods or properties from the constructor: ```kotlin open class A { init { println("init A") } open fun f() = "A" } class B : A() { private val s = "B" override fun f() = s init { println(f()) } }``` **Pros:** - No access to uninitialized data; - Guaranteed order of initialization. **Cons:** - Increases the amount of code for correct initialization.