Kotlin Inheritance

Kotlin inheritance is a fundamental object-oriented programming concept that allows you to create new classes based on existing ones. In Kotlin inheritance, a child class (subclass or derived class) can inherit properties and functions from a parent class (superclass or base class), enabling code reusability and establishing hierarchical relationships between classes. This comprehensive guide will teach you everything about Kotlin inheritance, from basic syntax to advanced inheritance patterns.

Understanding Kotlin Inheritance Fundamentals

Kotlin inheritance works differently from Java in several key ways. By default, all classes in Kotlin are final, meaning they cannot be inherited unless explicitly marked with the open keyword. This design choice helps prevent the fragile base class problem and makes your code more robust.

Basic Kotlin Inheritance Syntax

To enable inheritance in Kotlin, you must:

  1. Mark the parent class with the open keyword
  2. Use a colon (:) to specify inheritance
  3. Call the parent class constructor

Here’s the basic structure:

open class ParentClass {  
    // Parent class properties and methods  
}  
  
class ChildClass : ParentClass() {  
    // Child class specific properties and methods  
}  

The open Keyword in Kotlin Inheritance

The open keyword is crucial for Kotlin inheritance. It tells the compiler that a class, property, or function can be inherited or overridden by subclasses.

open class Vehicle {  
    open val wheels: Int = 4  
    open fun start() {  
        println("Vehicle is starting")  
    }  
      
    fun stop() {  
        println("Vehicle stopped")  // This cannot be overridden (not open)  
    }  
}  

Kotlin Inheritance with Primary Constructors

When working with Kotlin inheritance and primary constructors, the child class must initialize the parent class constructor.

open class Animal(val name: String, val species: String) {  
    open fun makeSound() {  
        println("$name makes a sound")  
    }  
}  
  
class Dog(name: String, val breed: String) : Animal(name, "Canine") {  
    override fun makeSound() {  
        println("$name barks: Woof! Woof!")  
    }  
      
    fun wagTail() {  
        println("$name is wagging tail")  
    }  
}  

In this example, the Dog class inherits from Animal and passes the name parameter to the parent constructor while adding its own breed property.

Kotlin Inheritance with Secondary Constructors

When dealing with secondary constructors in Kotlin inheritance, each secondary constructor must call the parent class constructor using the super keyword.

open class Electronic(val brand: String) {  
    var isOn: Boolean = false  
      
    constructor(brand: String, model: String) : this(brand) {  
        println("Electronic device: $brand $model")  
    }  
      
    open fun powerOn() {  
        isOn = true  
        println("$brand device is now ON")  
    }  
}  
  
class Smartphone : Electronic {  
    val operatingSystem: String  
      
    constructor(brand: String, os: String) : super(brand) {  
        this.operatingSystem = os  
        println("Smartphone created with $os")  
    }  
      
    constructor(brand: String, model: String, os: String) : super(brand, model) {  
        this.operatingSystem = os  
        println("Smartphone model: $model with $os")  
    }  
      
    override fun powerOn() {  
        super.powerOn()  
        println("Loading $operatingSystem interface...")  
    }  
}  

Method Overriding in Kotlin Inheritance

Method overriding is a key feature of Kotlin inheritance that allows child classes to provide specific implementations of parent class methods.

Override Requirements

  1. Parent method must be marked open
  2. Child method must use override keyword
  3. Method signature must match exactly
open class Shape {  
    open fun calculateArea(): Double {  
        return 0.0  
    }  
      
    open fun draw() {  
        println("Drawing a generic shape")  
    }  
}  
  
class Circle(private val radius: Double) : Shape() {  
    override fun calculateArea(): Double {  
        return Math.PI * radius * radius  
    }  
      
    override fun draw() {  
        println("Drawing a circle with radius $radius")  
    }  
}  
  
class Rectangle(private val width: Double, private val height: Double) : Shape() {  
    override fun calculateArea(): Double {  
        return width * height  
    }  
      
    override fun draw() {  
        super.draw()  // Call parent implementation  
        println("Drawing a rectangle ${width}x${height}")  
    }  
}  

Property Overriding in Kotlin Inheritance

Kotlin inheritance allows you to override properties just like methods. You can override val properties with either val or var, but you cannot override var properties with val.

open class Employee {  
    open val department: String = "General"  
    open val salary: Double = 50000.0  
}  
  
class Developer : Employee() {  
    override val department: String = "Engineering"  
    override var salary: Double = 80000.0  // val overridden with var  
      
    val programmingLanguages = listOf("Kotlin", "Java", "Python")  
}  
  
class Manager : Employee() {  
    override val department: String = "Management"  
    override val salary: Double = 100000.0  
      
    fun conductMeeting() {  
        println("Conducting team meeting in $department")  
    }  
}  

Using super Keyword in Kotlin Inheritance

The super keyword allows you to access parent class members from child classes.

open class GameCharacter(val name: String, var health: Int) {  
    open fun attack(): Int {  
        return 10  
    }  
      
    open fun takeDamage(damage: Int) {  
        health -= damage  
        println("$name takes $damage damage. Health: $health")  
    }  
}  
  
class Warrior(name: String, health: Int, private val weaponDamage: Int) : GameCharacter(name, health) {  
    override fun attack(): Int {  
        val baseDamage = super.attack()  // Get parent attack value  
        return baseDamage + weaponDamage  
    }  
      
    override fun takeDamage(damage: Int) {  
        val reducedDamage = damage - 5  // Warrior has armor  
        super.takeDamage(reducedDamage)  
    }  
      
    fun specialAttack(): Int {  
        println("$name performs a special attack!")  
        return attack() * 2  
    }  
}  

Multiple Inheritance with Interfaces

While Kotlin doesn’t support multiple class inheritance, you can implement multiple interfaces to achieve similar functionality.

interface Flyable {  
    fun fly() {  
        println("Flying through the air")  
    }  
}  
  
interface Swimmable {  
    fun swim() {  
        println("Swimming in water")  
    }  
}  
  
open class Bird(val species: String) {  
    open fun makeSound() {  
        println("Bird makes a sound")  
    }  
}  
  
class Duck(species: String) : Bird(species), Flyable, Swimmable {  
    override fun makeSound() {  
        println("$species says: Quack!")  
    }  
      
    override fun fly() {  
        println("$species flies with webbed feet tucked")  
    }  
      
    override fun swim() {  
        println("$species swims gracefully")  
    }  
}  

Resolving Conflicts in Multiple Inheritance

When implementing multiple interfaces with conflicting method names, you must override the method and specify which implementation to use.

interface A {  
    fun commonMethod() {  
        println("Implementation from A")  
    }  
}  
  
interface B {  
    fun commonMethod() {  
        println("Implementation from B")  
    }  
}  
  
class ConflictResolver : A, B {  
    override fun commonMethod() {  
        super<A>.commonMethod()  // Call A's implementation  
        super<B>.commonMethod()  // Call B's implementation  
        println("Custom implementation in ConflictResolver")  
    }  
}  

Abstract Classes in Kotlin Inheritance

Abstract classes provide partial implementations and force subclasses to implement abstract members.

abstract class Database {  
    abstract val connectionString: String  
    abstract fun connect()  
    abstract fun disconnect()  
      
    // Concrete method available to all subclasses  
    fun logConnection() {  
        println("Connecting to: $connectionString")  
    }  
}  
  
class MySQLDatabase(override val connectionString: String) : Database() {  
    override fun connect() {  
        logConnection()  
        println("MySQL connection established")  
    }  
      
    override fun disconnect() {  
        println("MySQL connection closed")  
    }  
      
    fun executeQuery(query: String) {  
        println("Executing MySQL query: $query")  
    }  
}  
  
class PostgreSQLDatabase(override val connectionString: String) : Database() {  
    override fun connect() {  
        logConnection()  
        println("PostgreSQL connection established")  
    }  
      
    override fun disconnect() {  
        println("PostgreSQL connection closed")  
    }  
      
    fun vacuum() {  
        println("Running PostgreSQL VACUUM")  
    }  
}  

Sealed Classes for Restricted Inheritance

Sealed classes provide controlled inheritance where all subclasses are known at compile time.

sealed class NetworkResult<out T> {  
    data class Success<T>(val data: T) : NetworkResult<T>()  
    data class Error(val exception: Throwable) : NetworkResult<Nothing>()  
    data object Loading : NetworkResult<Nothing>()  
}  
  
class ApiClient {  
    fun fetchUserData(userId: String): NetworkResult<User> {  
        return try {  
            // Simulate API call  
            val user = User(userId, "John Doe", "john@example.com")  
            NetworkResult.Success(user)  
        } catch (e: Exception) {  
            NetworkResult.Error(e)  
        }  
    }  
}  
  
data class User(val id: String, val name: String, val email: String)  
  
fun handleUserData(result: NetworkResult<User>) {  
    when (result) {  
        is NetworkResult.Success -> {  
            println("User loaded: ${result.data.name}")  
        }  
        is NetworkResult.Error -> {  
            println("Error: ${result.exception.message}")  
        }  
        is NetworkResult.Loading -> {  
            println("Loading user data...")  
        }  
    }  
    // No else clause needed - compiler knows all possibilities  
}  

Real-World Example: Building a Complete Inheritance Hierarchy

Let’s create a comprehensive example that demonstrates various Kotlin inheritance concepts:

// Abstract base class  
abstract class MediaFile(val name: String, val size: Long) {  
    abstract fun play()  
    abstract fun getFileType(): String  
      
    fun getFileInfo(): String {  
        return "File: $name, Size: ${size}MB, Type: ${getFileType()}"  
    }  
      
    open fun compress(): Boolean {  
        println("Compressing $name...")  
        return true  
    }  
}  
  
// Interface for downloadable files  
interface Downloadable {  
    val downloadUrl: String  
    fun download() {  
        println("Downloading from: $downloadUrl")  
    }  
}  
  
// Interface for streaming  
interface Streamable {  
    fun startStream() {  
        println("Starting stream...")  
    }  
      
    fun stopStream() {  
        println("Stopping stream...")  
    }  
}  
  
// Video file implementation  
class VideoFile(  
    name: String,  
    size: Long,  
    private val resolution: String,  
    private val duration: Int,  
    override val downloadUrl: String  
) : MediaFile(name, size), Downloadable, Streamable {  
      
    override fun play() {  
        println("Playing video: $name at $resolution resolution")  
        println("Duration: $duration minutes")  
    }  
      
    override fun getFileType(): String = "Video"  
      
    override fun compress(): Boolean {  
        println("Compressing video with H.264 codec...")  
        return super.compress()  
    }  
      
    fun extractThumbnail(): String {  
        return "${name}_thumbnail.jpg"  
    }  
}  
  
// Audio file implementation  
class AudioFile(  
    name: String,  
    size: Long,  
    private val bitrate: Int,  
    private val artist: String,  
    override val downloadUrl: String  
) : MediaFile(name, size), Downloadable {  
      
    override fun play() {  
        println("Playing audio: $name by $artist")  
        println("Bitrate: ${bitrate}kbps")  
    }  
      
    override fun getFileType(): String = "Audio"  
      
    fun showLyrics() {  
        println("Displaying lyrics for $name...")  
    }  
}  
  
// Document file (no downloading/streaming)  
class DocumentFile(  
    name: String,  
    size: Long,  
    private val pageCount: Int  
) : MediaFile(name, size) {  
      
    override fun play() {  
        println("Opening document: $name")  
        println("Pages: $pageCount")  
    }  
      
    override fun getFileType(): String = "Document"  
      
    override fun compress(): Boolean {  
        println("Compressing document using ZIP algorithm...")  
        return super.compress()  
    }  
      
    fun convertToPdf(): String {  
        return "${name.substringBeforeLast('.')}.pdf"  
    }  
}  
  
// Media player class that works with any MediaFile  
class MediaPlayer {  
    fun playFile(file: MediaFile) {  
        println("\n--- Media Player ---")  
        println(file.getFileInfo())  
        file.play()  
          
        // Handle downloadable files  
        if (file is Downloadable) {  
            file.download()  
        }  
          
        // Handle streamable files  
        if (file is Streamable) {  
            file.startStream()  
        }  
          
        file.compress()  
    }  
}  
  
// Usage example  
fun main() {  
    val videoFile = VideoFile(  
        name = "kotlin_tutorial.mp4",  
        size = 1024,  
        resolution = "1080p",  
        duration = 45,  
        downloadUrl = "https://example.com/kotlin_tutorial.mp4"  
    )  
      
    val audioFile = AudioFile(  
        name = "relaxing_music.mp3",  
        size = 8,  
        bitrate = 320,  
        artist = "Nature Sounds",  
        downloadUrl = "https://example.com/relaxing_music.mp3"  
    )  
      
    val documentFile = DocumentFile(  
        name = "kotlin_guide.pdf",  
        size = 2,  
        pageCount = 150  
    )  
      
    val mediaPlayer = MediaPlayer()  
      
    // Play different types of files  
    mediaPlayer.playFile(videoFile)  
    mediaPlayer.playFile(audioFile)  
    mediaPlayer.playFile(documentFile)  
      
    // Use specific methods  
    println("\n--- Specific Operations ---")  
    println("Video thumbnail: ${videoFile.extractThumbnail()}")  
    audioFile.showLyrics()  
    println("PDF conversion: ${documentFile.convertToPdf()}")  
}  

Expected Output:

--- Media Player ---  
File: kotlin_tutorial.mp4, Size: 1024MB, Type: Video  
Playing video: kotlin_tutorial.mp4 at 1080p resolution  
Duration: 45 minutes  
Downloading from: https://example.com/kotlin_tutorial.mp4  
Starting stream...  
Compressing video with H.264 codec...  
Compressing kotlin_tutorial.mp4...  
  
--- Media Player ---  
File: relaxing_music.mp3, Size: 8MB, Type: Audio  
Playing audio: relaxing_music.mp3 by Nature Sounds  
Bitrate: 320kbps  
Downloading from: https://example.com/relaxing_music.mp3  
Compressing relaxing_music.mp3...  
  
--- Media Player ---  
File: kotlin_guide.pdf, Size: 2MB, Type: Document  
Opening document: kotlin_guide.pdf  
Pages: 150  
Compressing document using ZIP algorithm...  
Compressing kotlin_guide.pdf...  
  
--- Specific Operations ---  
Video thumbnail: kotlin_tutorial.mp4_thumbnail.jpg  
Displaying lyrics for relaxing_music.mp3...  
PDF conversion: kotlin_guide.pdf  

This comprehensive example demonstrates:

  • Abstract class inheritance with MediaFile
  • Multiple interface implementation with Downloadable and Streamable
  • Method overriding in all subclasses
  • Use of super keyword for calling parent implementations
  • Polymorphism with the MediaPlayer class
  • Type checking with is operator
  • Real-world application structure

Key Points About Kotlin Inheritance

  1. Final by Default: All classes are final unless marked open
  2. Explicit Override: Use override keyword for overriding methods and properties
  3. Constructor Inheritance: Child classes must call parent constructors
  4. Multiple Interfaces: Implement multiple interfaces for flexible design
  5. Abstract Classes: Use for partial implementations and shared code
  6. Sealed Classes: Perfect for restricted hierarchies and type safety

Kotlin inheritance provides powerful tools for creating flexible, maintainable, and type-safe object-oriented code. By understanding these concepts and applying them correctly, you can build robust applications with clean, reusable code structures. For more information about Kotlin inheritance, visit the official Kotlin documentation.