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
- Design a graphics drawing system using inheritance to implement different shape types
- Create an employee management system demonstrating polymorphism in salary calculation
- Implement a game equipment system using abstract classes and interfaces to design weapons and armor
- Design a file processing system using sealed classes to represent different processing results
- Create a zoo management system demonstrating multiple inheritance and interface implementation