Interfaces, Abstract Classes and Inheritance

Learn about the interface, abstract classes, and inheritance in Kotlin.

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
}
Defining an interface

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)
}
Player class implements DiceRoller interface with custom rollDice() method

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)
}
Defines an interface DiceRoller with a default implementation of rollDice()

Let’s try it by running the code:

Press + to interact
import kotlin.random.*
fun main() {
// create an object that implements the DiceRoller interface
val 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 = 0
private var y: Int = 0
fun move(x: Int, y: Int) {
this.x = x
this.y = y
}
}
Defining an abstract class

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 {
...
}
Class ActivePlayer that extends Moveable and implements 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.

Press + to interact
Inheritance
Inheritance

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 {
...
}
Active player class with move and dice rolling functionality

Let’s try and override the move method now:

class ConfusedPlayer(name : String): Player(name) {
// move() must be declared open
override fun move(x: Int, y: Int) {
this.x = y // must be declared protected
this.y = x // must be declared protected
}
}
Subclass ConfusedPlayer overrides move() method and accesses protected properties

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:

Press + to interact
abstract class Moveable() {
protected var x: Int = 0
protected var y: Int = 0
open fun move(x: Int, y: Int) {
this.x = x
this.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:

Press + to interact
import kotlin.random.*
abstract class Moveable() {
protected var x: Int = 0
protected var y: Int = 0
open fun move(x: Int, y: Int) {
this.x = x
this.y = y
}
fun position() = "$x $y"
}
open class ActivePlayer(val name: String) : Moveable(), DiceRoller
class ConfusedPlayer(name: String) : ActivePlayer(name) {
// move() must be declared open
override fun move(x: Int, y: Int) {
this.x = y // must be declared protected
this.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)
}