Continuing with our series of Kotlin tutorials, today we’ll look into inheritance in Kotlin Classes. Let’s get started by creating a new IntelliJ IDEA Kotlin project.
Kotlin Inheritance
Inheritance is the concept of creating class hierarchies wherein we override properties and functions of the base class in its subclasses as per our needs.
All classes in Kotlin have a common superclass : Any
.
Inheritance allows code reusability. Typically the superclass is known as the base class or the parent class and the subclasses are the derived/child class.
Kotlin doesn’t allow a class to inherit from multiple classes because of famous diamond problem.
Kotlin Inheritance Example
Let’s put the above inheritance concept in our example code below.
We’ve created a base class as shown below:
open class Employee(eName: String) {
init {
println("He/She is an employee")
}
open var name = eName
var age: Int = 24
open fun printDetails() {
println("Employee Name is $name. No specific details are available in this function.")
}
}
By default all classes in Kotlin are final
. So to allow a class to be inherited, we need to attach the open
modifier before the class to make it non-final.
Also, to allow Properties and Functions to be overridden we need to set the open modifier on them too.
The init()
function is executed as soon as an instance of the class is created.
Following code contains a derived class that extends the above class.
open class Developer(dName: String, income: Int) : Employee(dName) {
init {
println("He/She is an a Developer")
}
override var name = dName
open var salary = income
override fun printDetails() {
println("Dev name is $name and salary is $salary")
}
}
- To inherit from class, we need to set the base class signature after
:
. - If the derived class(Developer) has a primary constructor, we need to initialize the base class(Employee) constructor right there using the parameters of the derived class.
- If the derived class doesn’t have a primary constructor, you need to call the base class constructor in the secondary constructor using the keyword
super
. - To override a property or a function from the base class which has exactly the same type, you’ll need to append them with
override
to prevent compilation errors. - You cannot declare a function/property in a derived class with the same signature if it is NOT open in the base class and/or you haven’t set the
override
modifier to it.
Let’s run the above codes in our main function in the Kotlin class to see how the program flow.
fun main(args: Array<String>) {
var employee = Employee("Anupam")
employee.printDetails()
println()
var developer = Developer("Jack", 10000)
developer.printDetails()
println(developer.age) //gets the age property from the superclass
}
The output should look like this:
- When an instance of the developer class is created, it first calls the
init
block of the superclass followed by its own. - The subclass can access all non-private properties and functions of its superclass.
- The superclass instance can be set to the subclass object. Doing so, the superclass can only access properties and functions in the subclass that are overridden.
The below code showcases setting the superclass instance to the subclass.
employee = developer
employee.printDetails() // Dev name is Jack and salary is 10000
println(employee.name) //Prints Jack
As it is evident in the above code, the superclass prints the overridden property and function.
Let’s created subclasses of the Developer
class now.
class AndroidDeveloper(name: String, income: Int) : Developer(name, income) {
override var name = "aName property value is $name".also(::println)
override var salary: Int = 0
get() = field
set(value) {
if (value >= 100000) {
field = min(50000, value)
} else field = value
}
init {
println("He/She is an Android Developer. If Salary >= 100000. It's halved.")
salary = income
}
fun works() {
println("Builds Apps")
}
override fun printDetails() {
super.printDetails()
println("He's an Android Developer")
}
}
class JuniorDeveloper : Developer {
var aName = "Name is $name".also(::println)
init {
println("He/She is a Junior Developer.")
}
var mySkill: String?
override var salary: Int = 0
get() = field
set(value) {
if (value > 50000) {
field = min(10000, value)
} else field = value
}
constructor(name: String, income: Int, skill: String) : super(name, income) {
mySkill = skill
salary = income
}
override fun printDetails() {
println("Junior dev name is $name and salary is $salary, Skill is $mySkill")
}
}
The JuniorDeveloper
class doesn’t have a primary constructor, so we call the superclass class in the secondary constructor using super
.
In both of the subclasses above we’ve set custom getters and setters on the overridden property salary
.
The setter property doesn’t get invoked when the property value is assigned for the first time in the definition itself.
Let’s run the class instances in the main function again.
val androidDeveloper = AndroidDeveloper("Rose", 100000)
androidDeveloper.printDetails()
androidDeveloper.salary = 100000
println("Updated Salary : ${androidDeveloper.salary}")
println("nJunior Developer class......n")
val juniorDeveloper = JuniorDeveloper("Brock", 60000, "Kotlin")
juniorDeveloper.printDetails()
juniorDeveloper.salary = 60000
println("Junior Dev new salary is ${juniorDeveloper.salary}")
println("Assigning juniordeveloper to super superclassn")
employee = Employee("Anupam")
employee = juniorDeveloper
print(employee.salary)
The following output is printed.
So for either of the instance creations, ALL the init
blocks of the superclasses get invoked first.
In the JuniorDeveloper class, we had to set the properties in the secondary constructor manually.
Hence, for the juniordeveloper
instance, the property setter gets instantly invoked.
In the last segment of the code, we’d set the employee
instance to the juniordeveloper
instance to fetch the overridden property.
The next example uses Interfaces and classes together in the class hierarchy.
open class Manager {
init {
println("He/She is a manager.")
}
open lateinit var mName: String
open var salary: Int? = null
constructor(name: String) {
mName = name
}
constructor(income: Int) {
salary = income
}
open fun printDetails() {
println("Name is $mName. Salary is $salary")
}
}
interface X {
fun printDetails() {
print("He has an X-Factor")
}
}
In the class, we’ve created two secondary constructors. The interface and the class have the same function signature. Let’s see how the subclasses deal with it.
class ProjectManager : Manager {
var numberOfProjects: Int?
var pName: String?
init {
println("He/She is a Project Manager.")
}
constructor(number: Int) : this("Ben", 10)
constructor(name: String, number: Int) : super(name) {
numberOfProjects = number
pName = "Congrats ${super.mName}"
}
override fun printDetails() {
super.printDetails()
println("$pName He's handled $numberOfProjects projects so far")
}
}
class BackendManager : Manager, X {
init {
println("He/She is a Backend Manager.")
}
var isBackendReady: Boolean?
val backendLanguage: String?
override var mName = "NA"
constructor(name: String, language: String) : super(name) {
println("Constructor 1 gets called.")
isBackendReady = false
backendLanguage = language
mName = name
}
constructor(isReady: Boolean, name: String) : this("Anupam", "PHP") {
println("Constructor 2 gets called.")
isBackendReady = isReady
}
override fun printDetails() {
super<Manager>.printDetails()
println("The backend he's working on, is it ready? $isBackendReady.")
super<X>.printDetails()
}
}
In the class(ProjectManager), the first constructor delegates to the second constructor using this
. So the second constructor would be executed before the first constructor.
super.mName
is used to access the property from the superclass.
Since it is an Optional
in this class we need to safely unwrap it using the ?.
operator.
Since the function signature is the same in the superclass and interface, to make a correct call, we need to do super<Type>.printDetails()
.
The output when the instances of the above classes are created is given below.
var manager = Manager("Thomas")
manager.printDetails()
println()
manager = ProjectManager(number = 10, name = "Ben")
manager.printDetails()
println()
val backendManager = BackendManager(true, "Smith")
backendManager.printDetails()
In the BackendManager class, the Constructor 2 is invoked, which first executes constructor 1.
That’s all for Kotlin inheritance example tutorial.
Reference: Kotlin Docs