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…