Kotlin Inheritance, Method Overriding, and Property Overriding

Inheritance is one of the key concepts of Object Oriented Programming (OOP). Inheritance enables re-usability. It allows a class to inherit features (properties and methods) from another class.

The class that inherits the features of another class is called the Child class or Derived class or Sub class, and the class whose features are inherited is called the Parent class or Base class or Super class.

All the classes in Kotlin have a common base class called Any. It corresponds to the Object class in Java. Every class that you create in Kotlin implicitly inherits from Any -

class Person // Implicitly inherits from the default Super class - Any

The Any class contains three methods namely equals(), hashCode() and toString(). All the classes in Kotlin inherit these three methods from Any, and can override them to provide their own implementation.

Inheritance (Creating Base and Derived classes)

Here is how you declare a base class and a derived class in Kotlin -

// Base class (Super class)
open class Computer {
}

// Derived class (Sub class)
class Laptop: Computer() {
}

Notice the use of open keyword in the base class. By default, all the classes in Kotlin are final (non-inheritable).

To allow a class to be inherited by others, you must mark it with the open modifier.

Note that the child class has the responsibility to initialize the parent class. If the child class has a primary constructor, then it must initialize the parent class right in the class header with the parameters passed to its primary constructor -

// Parent class
open class Computer(val name: String,
                    val brand: String) {
}

// Child class (initializes the parent class)
class Laptop(name: String, 
             brand: String, 
             val batteryLife: Double) : Computer(name, brand) {
   
}

If the child class doesn’t have a primary constructor, then all of its secondary constructors have to initialize the parent class either by calling the super keyword directly or by delegating to another constructor that does that -

class Laptop : Computer {
    val batteryLife: Double

	// Calls super() to initialize the Parent class
    constructor(name: String, brand: String, batteryLife: Double): super(name, brand) {
        this.batteryLife = batteryLife
    }

	// Calls another constructor (which calls super())
    constructor(name: String, brand: String): this(name, brand, 0.0) {
        
    }
}

In the above examples, we initialized the parent class using its primary constructor. If the parent class contains one or more secondary constructors, then the child class can initialize the parent class using any of the primary constructor or secondary constructors.

Just keep in mind that the parent class needs to be initialized. It doesn’t matter which of its constructor is used to initialize it.

Inheritance Example with Properties and Member Functions

Let’s now see a complete example of Inheritance in Kotlin. Consider a banking application where people can have several types of Bank accounts like SavingsAccount, CurrentAccount etc.

In such cases, it makes sense to create a base class called BankAccount and let other classes like SavingsAccount and CurrentAccount inherit from the BankAccount class.

Following is a simple BankAccount class for our Banking application -

/**
 * BankAccount (Base Class)
 * @property accountNumber - Account Number (read-only)
 * @property accountName -  Account Name (read-only)
 * @property balance - Current Balance (Mutable)
 */

open class BankAccount(val accountNumber: String, val accountName: String) {
    var balance : Double = 0.0

    fun depositeMoney(amount: Double): Boolean {
        if(amount > 0) {
            balance += amount
            return true
        } else {
            return false
        }
    }

    fun withdrawMoney(amount: Double): Boolean {
        if(amount > balance) {
            return false
        } else {
            balance -= amount
            return true
        }
    }

}

A Savings account is a Bank account with some interest rate on the balance amount. We can model the SavingsAccount class in the following way -

/**
 * SavingsAccount (Derived Class)
 * @property interestRate - Interest Rate for SavingsAccount (read-only)
 * @constructor - Primary constructor for creating a Savings Account
 * @param accountNumber - Account Number (used to initialize BankAccount)
 * @param accountName - Account Name (used to initialize BankAccount)
 */

class SavingsAccount (accountNumber: String, accountName: String, val interestRate: Double) :
        BankAccount(accountNumber, accountName) {

    fun depositInterest() {
        val interest = balance * interestRate / 100
        this.depositeMoney(interest);
    }
}

The SavingsAccount class inherits the following features from the base class -

  • Properties - accountNumber, accountName, balance
  • Methods - depositMoney, withdrawMoney

Let’s now write some code to test the above classes and methods -

fun main(args: Array<String>) {
    // Create a Savings Account with 6% interest rate
    val savingsAccount = SavingsAccount("64524627", "Rajeev Kumar Singh", 6.0)
    
    savingsAccount.depositeMoney(1000.0)
    
    savingsAccount.depositInterest()
    
    println("Current Balance = ${savingsAccount.balance}")
}

Overriding Member Functions

Just like Kotlin classes, members of a Kotlin class are also final by default. To allow a member function to be overridden, you need to mark it with the open modifier.

Moreover, The derived class that overrides a base class function must use the override modifier, otherwise, the compiler will generate an error -

open class Teacher {
    // Must use "open" modifier to allow child classes to override it
    open fun teach() {
        println("Teaching...")
    }
}

class MathsTeacher : Teacher() {
    // Must use "override" modifier to override a base class function
    override fun teach() {
        println("Teaching Maths...")
    }
}

Let’s test the above classes by defining the main method -

fun main(args: Array<String>) {
    val teacher = Teacher()
    val mathsTeacher = MathsTeacher()

    teacher.teach()  // Teaching...
    mathsTeacher.teach() // Teaching Maths..
}

Dynamic Polymorphism

Polymorphism is an important concept in Object Oriented Programming. There are two types of polymorphism -

  1. Static (compile-time) Polymorphism
  2. Dynamic (run-time) Polymorphism

Static polymorphism occurs when you define multiple overloaded functions with same name but different signatures. It is called compile-time polymorphism because the compiler can decide which function to call at compile itself.

Dynamic polymorphism occurs in case of function overriding. In this case, the function that is called is decided at run-time.

Here is an example -

fun main(args: Array<String>) {
    val teacher1: Teacher = Teacher()  // Teacher reference and object
    val teacher2: Teacher = MathsTeacher() // Teacher reference but MathsTeacher object

    teacher1.teach()  // Teaching...
    teacher2.teach() // Teaching Maths..
}

The line teacher2.teach() calls teach() function of MathsTeacher class even if teacher2 is of type Teacher. This is because teacher2 refers to a MathsTeacher object.

Overriding Properties

Just like functions, you can override the properties of a super class as well. To allow child classes to override a property of a parent class, you must annotate it with the open modifier.

Moreover, The child class must use override keyword for overriding a property of a parent class -

open class Employee {
    // Use "open" modifier to allow child classes to override this property
    open val baseSalary: Double = 30000.0
}

class Programmer : Employee() {
    // Use "override" modifier to override the property of base class
    override val baseSalary: Double = 50000.0
}

fun main(args: Array<String>) {
    val employee = Employee()
    println(employee.baseSalary) // 30000.0

    val programmer = Programmer()
    println(programmer.baseSalary) // 50000.0
}

Overriding Property’s Getter/Setter method

You can override a super class property either using an initializer or using a custom getter/setter.

In the example below, we’re overriding the age property by defining a custom setter method -

open class Person {
    open var age: Int = 1
}

class CheckedPerson: Person() {
    override var age: Int = 1
        set(value) {
            field = if(value > 0) value else throw IllegalArgumentException("Age can not be negative")
        }
}

fun main(args: Array<String>) {
    val person = Person()
    person.age = -5 // Works

    val checkedPerson = CheckedPerson()
    checkedPerson.age = -5  // Throws IllegalArgumentException : Age can not be negative
}

Calling properties and functions of Super class

When you override a property or a member function of a super class, the super class implementation is shadowed by the child class implementation.

You can access the properties and functions of the super class using super() keyword.

Here is an example -

open class Employee {
    open val baseSalary: Double = 10000.0

    open fun displayDetails() {
        println("I am an Employee")
    }
}

class Developer: Employee() {
    override var baseSalary: Double = super.baseSalary + 10000.0

    override fun displayDetails() {
        super.displayDetails()
        println("I am a Developer")
    }
}

Conclusion

That’s all in this article folks. I hope you understood how inheritance works in Kotlin. Please feel free to ask any doubts in the comment section below.