RSS Amplifier

Ashraf Latif · Dec 17, 2025

Learning Kotlin: Derived class initialization order

0
Sign in to vote or save

Ashraf Latif · Ashraf Latif

Notes: Class inheritance

  • By default, Kotlin classes are final – they can’t be inherited. To make a class inheritable, mark it with the `open` keyword:

  • Opened class make subclass inherits its methods & properties

    • But it doesn’t means it can be overrided

    • to override use `override` modifier

Let go through what happen for this code

open class Base(val name: String) {
    init { println("Initializing a base class") }
    open val size: Int =
        name.length.also { println("Initializing size in the base class: $it") }
}
class Derived(
    name: String,
    val lastName: String,
) : Base(name.replaceFirstChar { it.uppercase() }.also { println("Argument for the base class: $it") }) {
    init { println("Initializing a derived class") }
    override val size: Int =
        (super.size + lastName.length).also { println("Initializing size in the derived class: $it") }
}

Rule:

  • The base class is initialized before the devied class — but overriden members already belong to the derived class

Let assume this call:

Derived("john", "doe")

Step 1: Arguments to `Base(…)` are evaluated FIRST

name.replaceFirstChar { it.uppercase() }.also { println("Argument for the base class: $it") }
  • “john” → “John”

  • `also {}` prints

  • Value “John” is produced

output:

Argument for the base class: John

p/s: no constructor yet — just argument evaluation

Step 2: Base constructor starts

  • Now kotlin enters Base

Step 3: `Base` `init` blocks run (top to bottom)

init { println("Initializing a base class") }

output:

Initializing a base class

Step 4: `Base` properties are initialized (top to bottom)

open val size: Int = name.length.also { println(...) }

BUT `size` is open and overriden. So instead of use `Base.size` directly, kotlin dispatches to `Derived.size` even though `Derived` is not fully initialized yet.

Step 5: `Derived.size` is executed EARLY

(super.size + lastName.length)
    .also { println("Initializing size in the derived class: $it") }

What is safe at this point?

✔lastName → constructor property → initialized

✔super.size → Base implementation

❌ `Derived.init {}` → NOT run yet

❌`other derived properties → NOT initialized

output:

Initializing size in the derived class: 7

Step 6: Base constructor FINISHES

  • Now Base is done.

Step 7: Derived `init` blocks run

init { println("Initializing a derived class") }

output:

Initializing a derived class

Final output (in order)

Argument for the base class: John
Initializing a base class
Initializing size in the base class: 4
Initializing size in the derived class: 7
Initializing a derived class

Correct mental model

Think of it like this

1. Evaluate Base(...) arguments
2. Run Base.init
3. Initialize Base properties
     ↳ overridden property → Derived implementation
4. Finish Base
5. Run Derived.init

Kotlin initialization order diagram

CALL: Derived("john", "doe")
│
├─ 1️⃣ Evaluate Base(...) arguments
│   │
│   └─ name.replaceFirstChar { it.uppercase() }
│        .also { println(...) }
│
│      OUTPUT → "Argument for the base class: John"
│
├─ 2️⃣ Enter Base constructor
│   │
│   ├─ Base.init block
│   │    println("Initializing a base class")
│   │
│   │    OUTPUT → "Initializing a base class"
│   │
│   ├─ Base property initialization (top → bottom)
│   │
│   │    open val size = name.length
│   │           │
│   │           └── 🔥 OVERRIDE DETECTED
│   │                 dispatch to Derived.size
│   │
│   │                 super.size → 4
│   │                 lastName.length → 3
│   │
│   │                 println("Initializing size in the derived class: 7")
│   │
│   │                 OUTPUT → "Initializing size in the derived class: 7"
│   │
│   └─ Base constructor FINISHES
│
├─ 3️⃣ Enter Derived initialization
│   │
│   ├─ Derived.init block
│   │    println("Initializing a derived class")
│   │
│   │    OUTPUT → "Initializing a derived class"
│   │
└─ 4️⃣ Object is FULLY INITIALIZED

Kotlin said

Avoid using open members in constructors

What it REALLY means?

When a base class is being created, the derived class is NOT ready yet.
If the base class calls something that the derived class overrides, that derived code may run too early.

Just for fun fact, the code above can be safer like this

open class Base(val name: String) {
    // final by default
    val size: Int = name.length.also { println("Base size: $it") }
    init { println("Initializing Base with name: $name") }
}
class Derived(
    name: String,
    val lastName: String
) : Base(name.replaceFirstChar { it.uppercase() }
          .also { println("Argument for Base: $it") }) {
    val fullNameLength: Int = (size + lastName.length).also {
        println("Full name length in Derived: $it")
    }
    init { println("Initializing Derived with lastName: $lastName") }
}

output:

Argument for Base: John
Initializing Base with name: John
Base size: 4
Full name length in Derived: 7
Initializing Derived with lastName: doe

or also can use abstract class

abstract class Base(val name: String) {
    // Abstract, must be provided by subclass
    abstract val size: Int
    init { println("Initializing Base with name: $name") }
}
class Derived(
    name: String,
    val lastName: String
) : Base(name.replaceFirstChar { it.uppercase() }
          .also { println(Argument for Base: $it") }) {
    // Provide the size safely here
    override val size: Int = (name.length + lastName.length).also {
        println("Initializing size in Derived: $it")
    }
    init { println("Initializing Derived with lastName: $lastName") }
}

output:

Argument for Base: John
Initializing Base with name: John
Initializing size in Derived: 7
Initializing Derived with lastName: doe

No posts

Read the original on ceghap.substack.com

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.