iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Inheritance

Kotlin inheritance: open classes, abstract classes, interfaces, sealed types, and the rules that prevent fragile hierarchies.

Kotlin — inheritance

EXAMPLE
// ===== Classes are FINAL by default =====
class Animal { ... }
// class Dog : Animal() // error: Animal is final

// To subclass: mark the parent open
open class Animal { open fun speak() = println("generic") }
class Dog : Animal() {
    override fun speak() = println("woof")
}

// ===== Constructor inheritance =====
open class Animal(val name: String) {
    init { println("animal $name") }
}
class Dog(name: String, val breed: String) : Animal(name) {
    init { println("dog $breed") }
}

// Secondary constructor:
class Cat : Animal {
    constructor(name: String) : super(name)
    constructor() : super("unknown")
}

// ===== Abstract classes =====
abstract class Shape {
    abstract fun area(): Double
    open fun describe() = "shape with area ${area()}"
}
class Square(val side: Double) : Shape() {
    override fun area() = side * side
}

// ===== Interfaces =====
interface Greetable {
    val message: String
    fun greet() = println(message)    // default impl
}
interface Loggable { fun log() }

class User(val name: String) : Greetable, Loggable {
    override val message = "Hi $name"
    override fun log() = println("user $name")
}

// ===== Sealed classes (closed hierarchies) =====
sealed class Result<out T> {
    data class Ok<T>(val value: T) : Result<T>()
    data class Err(val msg: String) : Result<Nothing>()
}

// when on sealed types is EXHAUSTIVE — compiler enforces it:
fun handle(r: Result<Int>) = when (r) {
    is Result.Ok -> r.value
    is Result.Err -> -1
}

// ===== Object (singleton) =====
object Logger {
    fun log(msg: String) = println("[LOG] $msg")
}
Logger.log("hello")

// Companion object (static-like):
class MyClass {
    companion object {
        const val MAX = 100
        fun create() = MyClass()
    }
}
val x = MyClass.create()

// ===== Delegation =====
interface Repository<T> { fun find(id: Int): T? }
class InMemoryRepo<T>(private val store: Map<Int, T>) : Repository<T> {
    override fun find(id: Int) = store[id]
}

// Class delegation via 'by':
class CachedRepo<T>(repo: Repository<T>) : Repository<T> by repo

// 'by' delegates all interface methods to repo automatically.

// ===== Method overrides =====
open class Base {
    open fun greet() = println("base")
    fun final() = println("final")   // not overridable
}
class Sub : Base() {
    override fun greet() = println("sub")
    // override fun final() {}     // error
}

// ===== Visibility =====
// public (default), internal (module-private), protected (subclasses), private

// ===== Patterns to internalise =====
// - Classes final by default; open them only when designed for inheritance
// - Sealed classes for closed hierarchies (with exhaustive when)
// - Composition + delegation (by) over deep inheritance
// - Interfaces with default methods over abstract base classes

// ===== Pitfalls =====
// - Marking everything 'open' for testing convenience -> brittle code
// - Multiple inheritance via interfaces with conflicting defaults -> compile errors
// - Calling overridable methods from a constructor (subclass not yet initialised)
// - Sealed classes spread across files (all subclasses must be in the same MODULE or file)

Why it matters

Kotlin makes inheritance opt-in: final by default, open when designed. Sealed for closed hierarchies, interfaces with default methods over abstract bases, delegation via by for composition. The discipline keeps hierarchies shallow and prevents fragile-base-class pain.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
open class Animal {
    open fun speak() = "…"
}
class Dog : Animal() {
    override fun speak() = "woof"
}
Try it Yourself »

Discussion

Loading…