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: Johnp/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 classStep 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: 7Step 6: Base constructor FINISHES
Now Base is done.
Step 7: Derived `init` blocks run
init { println("Initializing a derived class") }output:
Initializing a derived classFinal 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 classCorrect 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.initKotlin 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 INITIALIZEDKotlin 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: doeor 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

