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 -
- Static (compile-time) Polymorphism
- 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.