Interfaces, Abstract Classes and Inheritance
Learn about the interface, abstract classes, and inheritance in Kotlin.
We'll cover the following
Let’s explore the concepts of interfaces, abstract classes and inheritance.
Interfaces
We are already familiar with the concept of interfaces from other languages. But let’s quickly recap.
In typed languages, interfaces provide a way to define behavior that some class will have to implement. The keyword to define an interface is simply interface
.
Let’s now define an interface for rolling a die:
interface DiceRoller {fun rollDice(): Int}
To implement the interface, a class specifies its name after a colon. There’s no keyword for this purpose in Kotlin like implement
in Java.
import kotlin.random.*class Player(...) : DiceRoller{...fun rollDice() = Random.nextInt(0, 6)}
This is also the first time we see the import
keyword. As the name implies, it allows us to import another package, such as kotlin.random
, from the Kotlin standard library.
Interfaces in Kotlin also support default functions. If a function doesn’t rely on any state, such as the rollDice()
function that simply rolls a random number between 0
and 5
, we can move it into the interface:
interface DiceRoller {fun rollDice() = Random.nextInt(0, 6)}
Let’s try it by running the code:
import kotlin.random.*fun main() {// create an object that implements the DiceRoller interfaceval diceRoller = object : DiceRoller {}val rollResult = diceRoller.rollDice()println("Rolling the dice... The result is $rollResult")}interface DiceRoller {fun rollDice(): Int = Random.nextInt(0, 6)}
Abstract classes
Abstract classes, another concept familiar to many, are similar to interfaces in that they cannot be instantiated directly. Another class must extend them first. The difference is that unlike interface
, an abstract class
can contain state.
Let’s create an abstract class that is able to move our player on the board or, for the sake of simplicity, just store the new coordinates:
abstract class Moveable() {private var x: Int = 0private var y: Int = 0fun move(x: Int, y: Int) {this.x = xthis.y = y}}
Any class that implements Moveable
will inherit a move()
function as well.
Now, let’s discuss in some more detail the private
keyword we see here for the first time.
Visibility modifiers
The private
properties or functions are only accessible to the class that declared them—Moveable
, in this case.
The default visibility of classes and properties is public, so there is no need to use the public
keyword all the time.
In order to extend an abstract class, we simply put its name after a colon. There’s also no keyword for this purpose in Kotlin like extends
in Java.
class ActivePlayer(name: String) : Moveable(), DiceRoller {...}
How would we be able to differentiate between an abstract class and an interface, then?
An abstract class has round brackets after its name to indicate that it has a constructor.
Inheritance
Apart from extending abstract classes, we can also extend regular classes as well.
Let’s try to extend our Player
class using the same syntax we used for an abstract class. We will attempt to create a ConfusedPlayer
class, that is, a player that when given
(x
and y
) moves to (y
and x
) instead.
First, let’s just create a class that inherits from Player
:
class ConfusedPlayer(name: String ): ActivePlayer(name)
Here, we can see the reason for round brackets even in abstract classes. This allows passing arguments to the parent class constructor. This is similar to using the super
keyword in Java.
Surprisingly, this doesn’t compile. The reason for this is that all classes in Kotlin are final
by default and cannot be inherited from.
To allow other classes to inherit from them, we need to declare them open
:
open class ActivePlayer (...) : Moveable(), DiceRoller {...}
Let’s try and override the move method now:
class ConfusedPlayer(name : String): Player(name) {// move() must be declared openoverride fun move(x: Int, y: Int) {this.x = y // must be declared protectedthis.y = x // must be declared protected}}
Overriding allows us to redefine the behavior of a function from a parent class. Whereas in Java, @Override
is an optional annotation, in Kotlin override
is a mandatory keyword. We cannot hide supertype methods, and code that doesn’t use override
explicitly won’t compile.
There are two other problems that we introduced in the above piece of code.
First, we cannot override a method that is not declared open
as well. Second, we cannot modify the coordinates of our player from a child class since both coordinates are private
.
Let’s use the protected
visibility modifier that makes the properties accessible to child classes and mark the function as open
to be able to override it:
abstract class Moveable() {protected var x: Int = 0protected var y: Int = 0open fun move(x: Int, y: Int) {this.x = xthis.y = y}}
Now, both of the problems are fixed. The protected
keyword here is used for the first time. Similar to Java, this visibility modifier makes a property or a method visible only to the class itself and its subclasses.
Let’s understand it better by running the code:
import kotlin.random.*abstract class Moveable() {protected var x: Int = 0protected var y: Int = 0open fun move(x: Int, y: Int) {this.x = xthis.y = y}fun position() = "$x $y"}open class ActivePlayer(val name: String) : Moveable(), DiceRollerclass ConfusedPlayer(name: String) : ActivePlayer(name) {// move() must be declared openoverride fun move(x: Int, y: Int) {this.x = y // must be declared protectedthis.y = x // must be declared protected}}fun main() {val player = ConfusedPlayer("Alex")val diceX = player.rollDice()val diceY = player.rollDice()println("Dice rolled $diceX $diceY")player.move(diceX, diceY)println("Player at ${player.position()}")}interface DiceRoller {fun rollDice(): Int = Random.nextInt(0, 6)}