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.
In each of the classes, you would be copying the same code to 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.
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
andname
, 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") }
- 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) { } }
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.