Kotlin Inheritance

Inheritance lets one class reuse the properties and methods of another class. The class that gives its features is the parent class (also called superclass or base class). The class that receives those features is the child class (also called subclass or derived class). This prevents code duplication — write once, share everywhere.

The Problem Inheritance Solves

Without inheritance:
class Dog {
    var name: String = ""
    fun breathe() { println("Breathing") }
    fun bark() { println("Woof!") }
}

class Cat {
    var name: String = ""
    fun breathe() { println("Breathing") }  // duplicate!
    fun meow() { println("Meow!") }
}

With inheritance:
open class Animal {
    var name: String = ""
    fun breathe() { println("Breathing") }
}

class Dog : Animal() {
    fun bark() { println("Woof!") }
}

class Cat : Animal() {
    fun meow() { println("Meow!") }
}

The open keyword is required on any class that can be inherited. By default, Kotlin classes are closed — they cannot be extended.

Diagram — Inheritance Hierarchy

        Animal (parent)
       name: String
       breathe()
       /         \
      /           \
  Dog (child)   Cat (child)
  bark()        meow()

Dog inherits: name, breathe()
Cat inherits: name, breathe()

Calling the Parent Constructor

open class Vehicle(val brand: String, val year: Int) {
    fun describe() {
        println("$brand ($year)")
    }
}

class ElectricCar(brand: String, year: Int, val range: Int) : Vehicle(brand, year) {
    fun showRange() {
        println("Range: $range km")
    }
}

val tesla = ElectricCar("Tesla", 2024, 500)
tesla.describe()    // Tesla (2024)
tesla.showRange()   // Range: 500 km

Overriding Methods

A child class can replace a parent's method with its own version. Mark the parent method with open and the child method with override.

open class Shape {
    open fun area(): Double = 0.0
    open fun describe() {
        println("I am a shape with area ${area()}")
    }
}

class Circle(val radius: Double) : Shape() {
    override fun area(): Double = 3.14159 * radius * radius
}

class Rectangle(val width: Double, val height: Double) : Shape() {
    override fun area(): Double = width * height
}

val c = Circle(5.0)
c.describe()    // I am a shape with area 78.53975

val r = Rectangle(4.0, 6.0)
r.describe()    // I am a shape with area 24.0

Calling the Parent Implementation with super

Use super to call the parent class version of a method from inside the child class.

open class Notification {
    open fun send() {
        println("Sending generic notification...")
    }
}

class PushNotification : Notification() {
    override fun send() {
        super.send()   // runs parent's send() first
        println("Also sending push to device.")
    }
}

PushNotification().send()
// Sending generic notification...
// Also sending push to device.

Overriding Properties

open class Device {
    open val maxVolume: Int = 10
}

class Headphones : Device() {
    override val maxVolume: Int = 20
}

println(Headphones().maxVolume)   // 20

Preventing Further Override with final

open class Animal {
    open fun sound() = println("...")
}

class Dog : Animal() {
    final override fun sound() = println("Woof!")
    // No further subclass can override sound()
}

Abstract Classes

An abstract class cannot be instantiated directly — you cannot create an object from it. It exists to be extended. Abstract methods have no body; subclasses must provide the implementation.

abstract class Employee(val name: String) {
    abstract fun calculateSalary(): Double   // no body

    fun introduce() {
        println("I am $name, salary: ${calculateSalary()}")
    }
}

class FullTimeEmployee(name: String, val monthlySalary: Double)
    : Employee(name) {
    override fun calculateSalary() = monthlySalary
}

class Contractor(name: String, val hourlyRate: Double, val hours: Int)
    : Employee(name) {
    override fun calculateSalary() = hourlyRate * hours
}

FullTimeEmployee("Priya", 50000.0).introduce()
// I am Priya, salary: 50000.0

Contractor("Dev", 500.0, 80).introduce()
// I am Dev, salary: 40000.0

Diagram — Abstract vs Concrete

[Abstract] Employee
  name: String
  calculateSalary(): Double  ← no body, MUST be overridden
  introduce()                ← has body, inherited as-is

      ┌────────────────────┐
      ▼                    ▼
FullTimeEmployee       Contractor
calculateSalary()      calculateSalary()
= monthlySalary        = hourlyRate * hours

Any — The Root of All Classes

Every Kotlin class automatically extends the built-in Any class. Any provides three methods available on every object:

val s = "Kotlin"
println(s.equals("Kotlin"))    // true
println(s.hashCode())          // some integer
println(s.toString())          // Kotlin

Leave a Comment

Your email address will not be published. Required fields are marked *