#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
// 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
// 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
// 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
// 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
// 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
// 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
// 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
// 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
// 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