Skip to content

Inheritance and Polymorphism

Overview

Inheritance and polymorphism are core concepts of object-oriented programming. Kotlin provides powerful and flexible inheritance mechanisms, supporting class inheritance, interface implementation, and abstract classes. This chapter will detail Kotlin's inheritance system, polymorphism, and related best practices.

Class Inheritance Basics

Basic Inheritance Syntax

kotlin
// Base class (parent class)
open class Animal(val name: String, val species: String) {
    // Properties
    var age: Int = 0
    protected var health: Int = 100
    
    // Methods
    open fun eat(food: String) {
        println("$name is eating $food")
        health += 5
    }
    
    open fun sleep() {
        println("$name is sleeping")
        health += 10
    }
    
    // Method that cannot be overridden
    fun getInfo(): String {
        return "$name is a $species, age $age years, health $health"
    }
    
    // Virtual method (must be overridden by subclasses)
    open fun makeSound() {
        println("$name makes a sound")
    }
}

// Derived class (child class)
class Dog(name: String) : Animal(name, "canine") {
    // Subclass-specific property
    var breed: String = "unknown breed"
    
    // Override parent class method
    override fun makeSound() {
        println("$name barks")
    }
    
    override fun eat(food: String) {
        println("$name gobbles up $food")
        health += 8  // Dogs recover more health when eating
    }
    
    // Subclass-specific methods
    fun wagTail() {
        println("$name wags tail")
    }
    
    fun fetch(item: String) {
        println("$name fetches $item")
    }
}

class Cat(name: String) : Animal(name, "feline") {
    var isIndoor: Boolean = true
    
    override fun makeSound() {
        println("$name meows")
    }
    
    override fun sleep() {
        println("$name curls up to sleep")
        health += 15  // Cats recover more health when sleeping
    }
    
    fun purr() {
        println("$name purrs")
    }
    
    fun climb() {
        println("$name climbs to a high place")
    }
}

fun main() {
    println("=== Basic Inheritance Example ===")
    
    // Create objects
    val dog = Dog("Buddy")
    dog.breed = "Golden Retriever"
    dog.age = 3
    
    val cat = Cat("Whiskers")
    cat.age = 2
    cat.isIndoor = true
    
    // Use inherited methods
    println(dog.getInfo())
    dog.eat("dog food")
    dog.makeSound()
    dog.wagTail()
    dog.fetch("ball")
    
    println()
    println(cat.getInfo())
    cat.eat("cat food")
    cat.makeSound()
    cat.purr()
    cat.sleep()
    
    // Polymorphism demonstration
    println("\n=== Polymorphism Demonstration ===")
    val animals: List<Animal> = listOf(dog, cat)
    
    animals.forEach { animal ->
        println("Processing animal: ${animal.name}")
        animal.makeSound()  // Calls each overridden method
        animal.eat("generic food")
        println()
    }
}

Constructor Inheritance

kotlin
// Parent class with multiple constructors
open class Vehicle(val brand: String, val model: String) {
    var year: Int = 0
    var color: String = "white"
    
    // Secondary constructor
    constructor(brand: String, model: String, year: Int) : this(brand, model) {
        this.year = year
    }
    
    constructor(brand: String, model: String, year: Int, color: String) : this(brand, model, year) {
        this.color = color
    }
    
    open fun start() {
        println("$brand $model starting")
    }
    
    open fun getDescription(): String {
        return "$year $color $brand $model"
    }
}

// Child class inherits and extends constructors
class Car(brand: String, model: String, val doors: Int) : Vehicle(brand, model) {
    var fuelType: String = "gasoline"
    
    // Calling parent's secondary constructor
    constructor(brand: String, model: String, doors: Int, year: Int) : this(brand, model, doors) {
        this.year = year
    }
    
    constructor(brand: String, model: String, doors: Int, year: Int, color: String, fuelType: String) 
        : this(brand, model, doors, year) {
        this.color = color
        this.fuelType = fuelType
    }
    
    override fun start() {
        println("Car $brand $model ignition starting")
    }
    
    override fun getDescription(): String {
        return "${super.getDescription()}, $doors doors, fuel type: $fuelType"
    }
    
    fun honk() {
        println("$brand $model honks: Beep beep!")
    }
}

class Motorcycle(brand: String, model: String, val engineSize: Int) : Vehicle(brand, model) {
    var hasWindshield: Boolean = false
    
    constructor(brand: String, model: String, engineSize: Int, year: Int, hasWindshield: Boolean) 
        : this(brand, model, engineSize) {
        this.year = year
        this.hasWindshield = hasWindshield
    }
    
    override fun start() {
        println("Motorcycle $brand $model kick-starting")
    }
    
    override fun getDescription(): String {
        val windshield = if (hasWindshield) "with windshield" else "without windshield"
        return "${super.getDescription()}, ${engineSize}cc engine, $windshield"
    }
    
    fun wheelie() {
        println("$brand $model does a wheelie")
    }
}

fun main() {
    println("=== Constructor Inheritance Example ===")
    
    // Create objects using different constructors
    val car1 = Car("Toyota", "Corolla", 4)
    val car2 = Car("Honda", "Accord", 4, 2023)
    val car3 = Car("BMW", "X5", 4, 2023, "black", "gasoline")
    
    val motorcycle1 = Motorcycle("Yamaha", "R1", 1000)
    val motorcycle2 = Motorcycle("Harley", "Davidson", 1200, 2022, true)
    
    // Display vehicle information
    val vehicles = listOf(car1, car2, car3, motorcycle1, motorcycle2)
    
    vehicles.forEach { vehicle ->
        println(vehicle.getDescription())
        vehicle.start()
        
        // Type checking and specific method calls
        when (vehicle) {
            is Car -> vehicle.honk()
            is Motorcycle -> vehicle.wheelie()
        }
        println()
    }
}

Abstract Classes and Methods

Abstract Class Definition

kotlin
// Abstract class
abstract class Shape(val name: String) {
    // Concrete property
    var color: String = "black"
    
    // Abstract properties
    abstract val area: Double
    abstract val perimeter: Double
    
    // Concrete method
    fun displayInfo() {
        println("Shape: $name, Color: $color")
        println("Area: ${"%.2f".format(area)}")
        println("Perimeter: ${"%.2f".format(perimeter)}")
    }
    
    // Abstract method
    abstract fun draw()
    
    // Open method (can be overridden)
    open fun move(x: Double, y: Double) {
        println("Moving $name to ($x, $y)")
    }
}

// Concrete implementation classes
class Circle(val radius: Double) : Shape("Circle") {
    override val area: Double
        get() = Math.PI * radius * radius
    
    override val perimeter: Double
        get() = 2 * Math.PI * radius
    
    override fun draw() {
        println("Drawing a circle with radius $radius")
    }
    
    override fun move(x: Double, y: Double) {
        println("Circle rolling to ($x, $y)")
    }
}

class Rectangle(val width: Double, val height: Double) : Shape("Rectangle") {
    override val area: Double
        get() = width * height
    
    override val perimeter: Double
        get() = 2 * (width + height)
    
    override fun draw() {
        println("Drawing a ${width}x${height} rectangle")
    }
}

class Triangle(val base: Double, val height: Double, val side1: Double, val side2: Double) : Shape("Triangle") {
    override val area: Double
        get() = 0.5 * base * height
    
    override val perimeter: Double
        get() = base + side1 + side2
    
    override fun draw() {
        println("Drawing a triangle with base $base, height $height")
    }
}

// Further inheritance from abstract class
abstract class Polygon(name: String, val sides: Int) : Shape(name) {
    // Common method for polygons
    fun getSides(): Int = sides
    
    // Abstract method
    abstract fun getInteriorAngleSum(): Double
}

class RegularHexagon(val sideLength: Double) : Polygon("Regular Hexagon", 6) {
    override val area: Double
        get() = (3 * Math.sqrt(3.0) / 2) * sideLength * sideLength
    
    override val perimeter: Double
        get() = 6 * sideLength
    
    override fun draw() {
        println("Drawing a regular hexagon with side length $sideLength")
    }
    
    override fun getInteriorAngleSum(): Double = (sides - 2) * 180.0
}

fun main() {
    println("=== Abstract Class Example ===")
    
    // Create concrete shape objects
    val shapes: List<Shape> = listOf(
        Circle(5.0).apply { color = "red" },
        Rectangle(4.0, 6.0).apply { color = "blue" },
        Triangle(3.0, 4.0, 5.0, 5.0).apply { color = "green" },
        RegularHexagon(3.0).apply { color = "yellow" }
    )
    
    // Polymorphic operations
    shapes.forEach { shape ->
        shape.displayInfo()
        shape.draw()
        shape.move(10.0, 20.0)
        
        // Type-specific operations
        if (shape is Polygon) {
            println("Interior angle sum: ${shape.getInteriorAngleSum()} degrees")
        }
        
        println()
    }
    
    // Calculate total area
    val totalArea = shapes.sumOf { it.area }
    println("Total area of all shapes: ${"%.2f".format(totalArea)}")
}

Interface Implementation and Multiple Inheritance

Multiple Interface Implementation

kotlin
// Define multiple interfaces
interface Flyable {
    val maxAltitude: Double
    val wingSpan: Double
    
    fun takeOff() {
        println("Taking off to $maxAltitude meters altitude")
    }
    
    fun land() {
        println("Landing")
    }
    
    fun fly() {
        println("Flying at $maxAltitude meters altitude, wingspan $wingSpan meters")
    }
}

interface Swimmable {
    val maxDepth: Double
    val swimSpeed: Double
    
    fun dive() {
        println("Diving to $maxDepth meters depth")
    }
    
    fun surface() {
        println("Surfacing")
    }
    
    fun swim() {
        println("Swimming at $swimSpeed km/h")
    }
}

interface Walkable {
    val walkSpeed: Double
    
    fun walk() {
        println("Walking at $walkSpeed km/h")
    }
    
    fun run() {
        println("Running at ${walkSpeed * 2} km/h")
    }
}

// Abstract animal class
abstract class WildAnimal(val name: String, val habitat: String) {
    abstract fun eat()
    abstract fun makeSound()
    
    fun rest() {
        println("$name resting in $habitat")
    }
}

// Class implementing multiple interfaces
class Duck(name: String) : WildAnimal(name, "lake"), Flyable, Swimmable, Walkable {
    override val maxAltitude = 1000.0
    override val wingSpan = 0.8
    override val maxDepth = 5.0
    override val swimSpeed = 8.0
    override val walkSpeed = 3.0
    
    override fun eat() {
        println("$name eats aquatic plants and small fish")
    }
    
    override fun makeSound() {
        println("$name quacks")
    }
    
    // Can override interface's default implementation
    override fun fly() {
        println("$name flaps wings flying above the water")
    }
}

class Penguin(name: String) : WildAnimal(name, "Antarctica"), Swimmable, Walkable {
    override val maxDepth = 200.0
    override val swimSpeed = 25.0
    override val walkSpeed = 2.0
    
    override fun eat() {
        println("$name eats fish and krill")
    }
    
    override fun makeSound() {
        println("$name makes penguin calls")
    }
    
    override fun walk() {
        println("$name waddles")
    }
    
    fun slide() {
        println("$name slides on ice")
    }
}

class Eagle(name: String) : WildAnimal(name, "mountains"), Flyable, Walkable {
    override val maxAltitude = 5000.0
    override val wingSpan = 2.5
    override val walkSpeed = 1.0
    
    override fun eat() {
        println("$name hunts small animals")
    }
    
    override fun makeSound() {
        println("$name screeches")
    }
    
    fun hunt() {
        println("$name dives from high altitude to hunt")
    }
}

fun main() {
    println("=== Multiple Interface Implementation Example ===")
    
    val animals = listOf(
        Duck("Donald"),
        Penguin("Pingu"),
        Eagle("Eddie")
    )
    
    animals.forEach { animal ->
        println("=== ${animal.name} ===")
        animal.makeSound()
        animal.eat()
        animal.rest()
        
        // Perform different operations based on capabilities
        if (animal is Flyable) {
            animal.takeOff()
            animal.fly()
            animal.land()
        }
        
        if (animal is Swimmable) {
            animal.dive()
            animal.swim()
            animal.surface()
        }
        
        if (animal is Walkable) {
            animal.walk()
            animal.run()
        }
        
        // Specific methods
        when (animal) {
            is Penguin -> animal.slide()
            is Eagle -> animal.hunt()
        }
        
        println()
    }
    
    // Group by capability
    println("=== Grouping by Capability ===")
    val flyers = animals.filterIsInstance<Flyable>()
    val swimmers = animals.filterIsInstance<Swimmable>()
    val walkers = animals.filterIsInstance<Walkable>()
    
    println("Flying animals: ${flyers.map { (it as WildAnimal).name }}")
    println("Swimming animals: ${swimmers.map { (it as WildAnimal).name }}")
    println("Walking animals: ${walkers.map { (it as WildAnimal).name }}")
}

Deep Dive into Polymorphism

Method Overriding and Dynamic Binding

kotlin
// Base class
open class Employee(val name: String, val id: String) {
    open val baseSalary: Double = 50000.0
    
    open fun calculateSalary(): Double {
        return baseSalary
    }
    
    open fun getJobDescription(): String {
        return "Regular employee"
    }
    
    open fun work() {
        println("$name is working")
    }
    
    // Template method pattern
    fun dailyRoutine() {
        println("=== ${name}'s daily work ===")
        clockIn()
        work()
        takeBreak()
        work()
        clockOut()
        println("Salary: ${"%.2f".format(calculateSalary())}")
        println()
    }
    
    private fun clockIn() = println("$name clocks in")
    private fun takeBreak() = println("$name takes a break")
    private fun clockOut() = println("$name clocks out")
}

// Manager class
class Manager(name: String, id: String, val teamSize: Int) : Employee(name, id) {
    override val baseSalary: Double = 80000.0
    
    override fun calculateSalary(): Double {
        return baseSalary + (teamSize * 5000)  // Team bonus
    }
    
    override fun getJobDescription(): String {
        return "Team manager managing $teamSize people"
    }
    
    override fun work() {
        println("$name is managing the team and setting strategies")
    }
    
    fun holdMeeting() {
        println("$name holds team meeting")
    }
}

// Developer class
class Developer(name: String, id: String, val programmingLanguage: String) : Employee(name, id) {
    override val baseSalary: Double = 70000.0
    
    override fun calculateSalary(): Double {
        val languageBonus = when (programmingLanguage.lowercase()) {
            "kotlin", "scala" -> 10000.0
            "java", "python" -> 8000.0
            "javascript" -> 6000.0
            else -> 5000.0
        }
        return baseSalary + languageBonus
    }
    
    override fun getJobDescription(): String {
        return "$programmingLanguage developer"
    }
    
    override fun work() {
        println("$name is writing code in $programmingLanguage")
    }
    
    fun codeReview() {
        println("$name conducts code review")
    }
}

// Designer class
class Designer(name: String, id: String, val designTool: String) : Employee(name, id) {
    override val baseSalary: Double = 60000.0
    
    override fun calculateSalary(): Double {
        return baseSalary + 8000  // Creativity bonus
    }
    
    override fun getJobDescription(): String {
        return "UI/UX designer using $designTool"
    }
    
    override fun work() {
        println("$name is designing interfaces with $designTool")
    }
    
    fun createPrototype() {
        println("$name creates design prototype")
    }
}

// Company class demonstrating polymorphism
class Company {
    private val employees = mutableListOf<Employee>()
    
    fun hireEmployee(employee: Employee) {
        employees.add(employee)
        println("Hired ${employee.name}, position: ${employee.getJobDescription()}")
    }
    
    fun runDailyOperations() {
        println("=== Company Daily Operations ===")
        employees.forEach { employee ->
            employee.dailyRoutine()
        }
    }
    
    fun calculateTotalPayroll(): Double {
        return employees.sumOf { it.calculateSalary() }
    }
    
    fun getEmployeesByType(): Map<String, List<Employee>> {
        return employees.groupBy { it::class.simpleName ?: "Unknown" }
    }
}

fun main() {
    println("=== Polymorphism Demonstration ===")
    
    val company = Company()
    
    // Hire different types of employees
    company.hireEmployee(Manager("John Manager", "M001", 5))
    company.hireEmployee(Developer("Alice Developer", "D001", "Kotlin"))
    company.hireEmployee(Developer("Bob Programmer", "D002", "Python"))
    company.hireEmployee(Designer("Carol Designer", "DS001", "Figma"))
    
    // Run daily operations (polymorphism in action)
    company.runDailyOperations()
    
    // Calculate total payroll
    val totalPayroll = company.calculateTotalPayroll()
    println("Company total payroll: ${"%.2f".format(totalPayroll)}")
    
    // Statistics by type
    println("\n=== Employee Statistics ===")
    val employeesByType = company.getEmployeesByType()
    employeesByType.forEach { (type, employees) ->
        println("$type: ${employees.size} people")
        employees.forEach { employee ->
            println("  - ${employee.name}: ${employee.getJobDescription()}")
        }
    }
}

Sealed Classes and Inheritance

Using Sealed Classes

kotlin
// Sealed class defining state hierarchy
sealed class NetworkResult<out T> {
    object Loading : NetworkResult<Nothing>()
    data class Success<T>(val data: T) : NetworkResult<T>()
    data class Error(val exception: Throwable) : NetworkResult<Nothing>()
}

// Sealed class defining UI state
sealed class UiState {
    object Idle : UiState()
    object Loading : UiState()
    data class Content(val data: String) : UiState()
    data class Error(val message: String) : UiState()
}

// Sealed class defining user actions
sealed class UserAction {
    object Login : UserAction()
    object Logout : UserAction()
    data class UpdateProfile(val name: String, val email: String) : UserAction()
    data class SendMessage(val recipient: String, val message: String) : UserAction()
}

// Service class using sealed classes
class NetworkService {
    
    fun fetchUserData(userId: String): NetworkResult<User> {
        return try {
            // Simulate network request
            Thread.sleep(100)
            
            when (userId) {
                "1" -> NetworkResult.Success(User("1", "Alice", "alice@example.com"))
                "2" -> NetworkResult.Success(User("2", "Bob", "bob@example.com"))
                "error" -> NetworkResult.Error(RuntimeException("User not found"))
                else -> NetworkResult.Error(RuntimeException("Network error"))
            }
        } catch (e: Exception) {
            NetworkResult.Error(e)
        }
    }
    
    fun processNetworkResult(result: NetworkResult<User>): String {
        return when (result) {
            is NetworkResult.Loading -> "Loading user data..."
            is NetworkResult.Success -> "User data loaded successfully: ${result.data.name}"
            is NetworkResult.Error -> "Loading failed: ${result.exception.message}"
        }
    }
}

// UI controller class
class UiController {
    private var currentState: UiState = UiState.Idle
    
    fun updateState(newState: UiState) {
        currentState = newState
        renderState()
    }
    
    private fun renderState() {
        val stateDescription = when (currentState) {
            is UiState.Idle -> "UI idle state"
            is UiState.Loading -> "Loading..."
            is UiState.Content -> "Displaying content: ${(currentState as UiState.Content).data}"
            is UiState.Error -> "Error: ${(currentState as UiState.Error).message}"
        }
        println("UI state updated: $stateDescription")
    }
    
    fun handleUserAction(action: UserAction) {
        println("Handling user action: ${getActionDescription(action)}")
        
        when (action) {
            is UserAction.Login -> {
                updateState(UiState.Loading)
                // Simulate login process
                Thread.sleep(500)
                updateState(UiState.Content("User logged in"))
            }
            is UserAction.Logout -> {
                updateState(UiState.Content("User logged out"))
            }
            is UserAction.UpdateProfile -> {
                updateState(UiState.Loading)
                // Simulate update process
                Thread.sleep(300)
                updateState(UiState.Content("Profile updated"))
            }
            is UserAction.SendMessage -> {
                updateState(UiState.Loading)
                // Simulate message sending
                Thread.sleep(200)
                if (action.message.isNotBlank()) {
                    updateState(UiState.Content("Message sent to ${action.recipient}"))
                } else {
                    updateState(UiState.Error("Message cannot be empty"))
                }
            }
        }
    }
    
    private fun getActionDescription(action: UserAction): String {
        return when (action) {
            is UserAction.Login -> "User login"
            is UserAction.Logout -> "User logout"
            is UserAction.UpdateProfile -> "Update profile: ${action.name}, ${action.email}"
            is UserAction.SendMessage -> "Send message to ${action.recipient}: ${action.message}"
        }
    }
}

data class User(val id: String, val name: String, val email: String)

fun main() {
    println("=== Sealed Class Example ===")
    
    // Network service example
    val networkService = NetworkService()
    
    println("=== Network Request Example ===")
    val userIds = listOf("1", "2", "error", "timeout")
    
    userIds.forEach { userId ->
        println("Requesting user $userId data...")
        val result = networkService.fetchUserData(userId)
        println(networkService.processNetworkResult(result))
        println()
    }
    
    // UI controller example
    println("=== UI State Management Example ===")
    val uiController = UiController()
    
    val actions = listOf(
        UserAction.Login,
        UserAction.UpdateProfile("Alice", "alice@newdomain.com"),
        UserAction.SendMessage("Bob", "Hello Bob!"),
        UserAction.SendMessage("Charlie", ""),  // Empty message, will cause error
        UserAction.Logout
    )
    
    actions.forEach { action ->
        uiController.handleUserAction(action)
        Thread.sleep(100)  // Brief delay to observe state changes
        println()
    }
}

Best Practices

1. Inheritance Design Principles

kotlin
// Good inheritance design: following Liskov Substitution Principle
abstract class PaymentProcessor {
    abstract fun processPayment(amount: Double): Boolean
    
    // Template method
    fun executePayment(amount: Double): String {
        return if (validateAmount(amount) && processPayment(amount)) {
            "Payment successful"
        } else {
            "Payment failed"
        }
    }
    
    protected open fun validateAmount(amount: Double): Boolean {
        return amount > 0
    }
}

class CreditCardProcessor : PaymentProcessor() {
    override fun processPayment(amount: Double): Boolean {
        println("Processing credit card payment: $amount")
        return true
    }
}

class PayPalProcessor : PaymentProcessor() {
    override fun processPayment(amount: Double): Boolean {
        println("Processing PayPal payment: $amount")
        return true
    }
    
    override fun validateAmount(amount: Double): Boolean {
        return super.validateAmount(amount) && amount <= 10000  // PayPal limit
    }
}

2. Composition Over Inheritance

kotlin
// Using composition instead of deep inheritance
interface Engine {
    fun start()
    fun stop()
}

interface Transmission {
    fun shiftGear(gear: Int)
}

class PetrolEngine : Engine {
    override fun start() = println("Petrol engine starting")
    override fun stop() = println("Petrol engine stopping")
}

class AutomaticTransmission : Transmission {
    override fun shiftGear(gear: Int) = println("Automatic transmission shifting to gear $gear")
}

// Using composition
class ModernCar(
    private val engine: Engine,
    private val transmission: Transmission
) {
    fun start() {
        engine.start()
        transmission.shiftGear(1)
    }
    
    fun stop() {
        transmission.shiftGear(0)
        engine.stop()
    }
}

3. Using Sealed Classes Wisely

kotlin
// Use sealed classes to represent finite state sets
sealed class ApiResponse<out T> {
    object Loading : ApiResponse<Nothing>()
    data class Success<T>(val data: T) : ApiResponse<T>()
    data class Error(val code: Int, val message: String) : ApiResponse<Nothing>()
}

// Compiler ensures completeness of when expressions
fun handleResponse(response: ApiResponse<String>): String {
    return when (response) {
        is ApiResponse.Loading -> "Loading..."
        is ApiResponse.Success -> "Data: ${response.data}"
        is ApiResponse.Error -> "Error ${response.code}: ${response.message}"
        // No else branch needed, compiler guarantees completeness
    }
}

Next Steps

After mastering inheritance and polymorphism, let's learn about regular expression processing in Kotlin.

Next Chapter: Regular Expressions

Exercises

  1. Design a graphics drawing system using inheritance to implement different shape types
  2. Create an employee management system demonstrating polymorphism in salary calculation
  3. Implement a game equipment system using abstract classes and interfaces to design weapons and armor
  4. Design a file processing system using sealed classes to represent different processing results
  5. Create a zoo management system demonstrating multiple inheritance and interface implementation

Content is for learning and research only.