Kotlin Inheritance

In this article, you'll learn about inheritance. More specifically, what is inheritance and how to implement it in Kotlin (with the help of examples).

Inheritance is one of the key features of object-oriented programming. It allows user to create a new class (derived class) from an existing class (base class).

The derived class inherits all the features from the base class and can have additional features of its own.

Before going into details about Kotlin inheritance, we recommend you to check these two articles:


Why inheritance?

Suppose, in your application, you want three characters - a math teacher, a footballer and a businessman.

Since, all of the characters are persons, they can walk and talk. However, they also have some special skills. A math teacher can teach math, a footballer can play football and a businessman can run a business.

You can individually create three classes who can walk, talk and perform their special skill.

Example of classes sharing same features without the use of inheritance.

In each of the classes, you would be copying the same code for walk and talk for each character.

If you want to add a new feature - eat, you need to implement the same code for each character. This can easily become error prone (when copying) and duplicate codes.

It would be a lot easier if we had a Person class with basic features like talk, walk, eat, sleep, and add special skills to those features as per our characters. This is done using inheritance.

Example of inheritance in OOP

Using inheritance, now you don't implement the same code for walk(), talk() and eat() for each class. You just need to inherit them.

So, for MathTeacher (derived class), you inherit all features of a Person (base class) and add a new feature teachMath(). Likewise, for the Footballer class, you inherit all the features of the Person class and add a new feature playFootball() and so on.

This makes your code cleaner, understandable and extendable.

It is important to remember: When working with inheritance, each derived class should satisfy the condition whether it "is a" base class or not. In the example above, MathTeacher is a Person, Footballer is a Person. You cannot have something like, Businessman is a Business.


Kotlin inheritance

Let's try to implement the above discussion in code:

open class Person(age: Int) {
    // code for eating, talking, walking
}

class MathTeacher(age: Int): Person(age) {
    // other features of math teacher
}

class Footballer(age: Int): Person(age) {
    // other features of footballer
}

class Businessman(age: Int): Person(age) {
    // other features of businessman
}

Here, Person is a base class, and classes MathTeacher, Footballer, and Businessman are derived from the Person class.

Notice, the keyword open before the base class, Person. It's important.

By default, classes in Kotlin are final. If you are familiar with Java, you know that a final class cannot be subclassed. By using the open annotation on a class, compiler allows you to derive new classes from it.


Example: Kotlin Inheritance

open class Person(age: Int, name: String) {
    init {
        println("My name is $name.")
        println("My age is $age")
    }
}

class MathTeacher(age: Int, name: String): Person(age, name) {

    fun teachMaths() {
        println("I teach in primary school.")
    }
}

class Footballer(age: Int, name: String): Person(age, name) {
    fun playFootball() {
        println("I play for LA Galaxy.")
    }
}

fun main(args: Array<String>) {
    val t1 = MathTeacher(25, "Jack")
    t1.teachMaths()

    println()

    val f1 = Footballer(29, "Christiano")
    f1.playFootball()
}

When you run the program, the output will be:

My name is Jack.
My age is 25
I teach in primary school.

My name is Cristiano.
My age is 29
I play for LA Galaxy.

Here, two classes MathTeacher and Footballer are derived from the Person class.

The primary constructor of the Person class declared two properties: age and name, and it has an initializer block. The initilizer block (and member functions) of the base class Person can be accessed by the objects of derived classes (MathTeacher and Footballer).

Derived classes MathTeacher and Footballer have their own member functions teachMaths() and playFootball() respectively. These functions are accessible only from the objects of their respective class.


When the object t1 of MathTeacher class is created,

val t1 = MathTeacher(25, "Jack")

The parameters are passed to the primary constructor. In Kotlin, init block is called when the object is created. Since, MathTeacher is derived from Person class, it looks for initializer block in the base class (Person) and executes it. If the MathTeacher had init block, the compiler would have also executed the init block of the derived class.

Next, the teachMaths() function for object t1 is called using t1.teachMaths() statement.

The program works similarly when object f1 of Footballer class is created. It executes the init block of the base class. Then, the playFootball() method of Footballer class is called using statement f1.playFootball().


Important Notes: Kotlin Inheritance

  • If the class has a primary constructor, the base must be initialized using the parameters of the primary constructor. In the above program, both derived classes have two parameters age and name, and both these parameters are initialized in primary constructor in the base class.

    Here's another example:
    open class Person(age: Int, name: String) {
        // some code
    }
    
    class Footballer(age: Int, name: String, club: String): Person(age, name) {
        init {
            println("Football player $name of age $age and plays for $club.")
        }
    
        fun playFootball() {
            println("I am playing football.")
        }
    }
    
    fun main(args: Array<String>) {
        val f1 = Footballer(29, "Cristiano", "LA Galaxy")
    }
    
    
    Here the primary constructor of the derived class has 3 parameters, and the base class has 2 parameters. Note that, both parameters of the base class are initialized.
     
  • In case of no primary constructor, each base class has to initialize the base (using super keyword), or delegate to another constructor which does that. For example,
    fun main(args: Array<String>) {
    
        val p1 = AuthLog("Bad Password")
    }
    
    open class Log {
        var data: String = ""
        var numberOfData = 0
        constructor(_data: String) {
    
        }
        constructor(_data: String, _numberOfData: Int) {
            data = _data
            numberOfData = _numberOfData
            println("$data: $numberOfData times")
        }
    }
    
    class AuthLog: Log {
        constructor(_data: String): this("From AuthLog -> + $_data", 10) {
        }
    
        constructor(_data: String, _numberOfData: Int): super(_data, _numberOfData) {
        }
    }
    To learn more on how this program works, visit Kotlin Secondary Constructor.

Overriding Member Functions and Properties

If the base class and the derived class contains a member function (or property) with the same name, you can need to override the member function of the derived class using override keyword, and use open keyword for the member function of the base class.


Example: Overriding Member Function

// Empty primary constructor
open class Person() {
    open fun displayAge(age: Int) {
        println("My age is $age.")
    }
}

class Girl: Person() {

    override fun displayAge(age: Int) {
        println("My fake age is ${age - 5}.")
    }
}

fun main(args: Array<String>) {
    val girl = Girl()
    girl.displayAge(31)
}

When you run the program, the output will be:

My fake age is 26.

Here, girl.displayAge(31) calls the displayAge() method of the derived class Girl.


You can override property of the base class in similar way.

Visit how Kotlin getters and setters work in Kotlin before you check the example below.

// Empty primary constructor
open class Person() {
    open var age: Int = 0
        get() = field

        set(value) {
            field = value
        }
}

class Girl: Person() {

    override var age: Int = 0
        get() = field

        set(value) {
            field = value - 5
        }
}

fun main(args: Array<String>) {

    val girl = Girl()
    girl.age = 31
    println("My fake age is ${girl.age}.")
}

When you run the program, the output will be:

My fake age is 26.

As you can see, we have used override and open keywords for age property in derived class and base class respectively.


Calling Members of Base Class from Derived Class

You can call functions (and access properties) of the base class from a derived class using super keyword. Here's how:

open class Person() {
    open fun displayAge(age: Int) {
        println("My actual age is $age.")
    }
}

class Girl: Person() {

    override fun displayAge(age: Int) {

        // calling function of base class
        super.displayAge(age)
        
        println("My fake age is ${age - 5}.")
    }
}

fun main(args: Array<String>) {
    val girl = Girl()
    girl.displayAge(31)
}

When you run the program, the output will be:

My age is 31.
My fake age is 26.