Skip to content

DA3. Classes and Inheritance in Kotlin

Classes

  • Classes templates for creating objects, and spaces for organizing functions and variables in a logical way.
  • Kotlin classes can have multiple constructors, one primary constructor and one or more secondary constructors.
  • The multiple constructors allow the class to be initialized in different ways, and each one must call the primary constructor first.
  • The primary constructor can not execute code, so the init block is used to execute code after the primary constructor as it runs once and only once after the primary constructor for each instance of the class.
class Foo constructor(var count: Int, val con: String = 'Primary Constructor' ) { // primary constructor, with default value
    init {
        println("Foo created with count $count by constructor $con") // only executed once, after primary constructor
    }
    constructor(stringCount: String) : this(stringCount.toInt(), 'Constructor 1') { // call primary constructor
        println("Foo created with count $count") // only executed once, after primary constructor
        // execute more code if needed
    }
    constructor() : this(0, 'Constructor 2') // call primary constructor
}

val foo1 = Foo(1) // call primary constructor
val foo2 = Foo("2") // call secondary constructor
val foo3 = Foo() // call secondary constructor

/**
1. Foo created with count 1 by constructor Primary Constructor
2. Foo created with count 2 by constructor Constructor 1
2. Foo created with count 2
3. Foo created with count 0 by constructor Constructor 2
*/
  • In the code above:
    • The Foo class has a primary constructor with two parameters; count is mandatory and con is optional with a default value.
    • Two secondary constructors are defined, each one calls the primary constructor first using the this() keyword.

Inheritance

  • Inheritance is way of passing common functionality between classes, it allows for code reuse and organization and defining the contracts between classes (using interfaces or abstract classes).
  • A subclass (child) can extend (inherit) from a superclass (parent) using the : symbol.
  • The super class must be open to allow inheritance, and the subclass must call the super class constructor.
  • In Kotlin, a class can only inherit from one super class, but can implement multiple interfaces.
  • abstract classes are way for defining some methods/properties that must be implemented by the subclass.
open class Animal {}
class Dog : Animal() {}
class Cat : Animal() {}

fun classifyAnimal(animal: Animal): Unit = when (animal) {
    is Dog -> println("animal is a Dog")
    is Cat -> println("animal is a Cat")
    else -> println("animal is neither a Dog nor a Cat")
}

fun main() {
    val dog = Dog()
    val cat = Cat()
    val animal: Animal =
        Animal() // animal is a Dog, but declared as Animal; we can not tell what it is at compile time

    classifyAnimal(dog) // animal is a Dog
    classifyAnimal(cat) // animal is a Cat
    classifyAnimal(animal) // animal is a Dog
}
  • In the code above:
    • The Animal class is open to allow inheritance.
    • Dog and Cat inherit the Animal class.
    • The classifyAnimal function takes an Animal as a parameter, and uses the when expression to check the type of the animal.

Enums

  • Enums are a way of defining a type with a fixed set of values, or placing constraints on the values of a type.
  • Kotlin is a bit weird when dealing with enums, as they are considered Full Classes and can have properties and methods, while all other languages consider enums as Restricted values of integers.
  • Almost all class features are available in enums, except for inheritance and few other rules.
enum class PossibleShapes (val sides: Int, sideNames: List<String>) {
    Circle(1, listOf("radius")),
    Rectangle(4, listOf("width", "height")),
    Triangle(3, listOf("base", "height", "hypotenuse"))
}

fun describeShape(shape: PossibleShapes): Unit = when (shape) {
    PossibleShapes.Circle -> println("Circle has ${shape.sides} side(s): ${shape.sideNames}")
    PossibleShapes.Rectangle -> println("Rectangle has ${shape.sides} side(s): ${shape.sideNames}")
    PossibleShapes.Triangle -> println("Triangle has ${shape.sides} side(s): ${shape.sideNames}")
}

fun main() {
    describeShape(PossibleShapes.Circle) // Circle has 1 side(s): [radius]
    describeShape(PossibleShapes.Rectangle) // Rectangle has 4 side(s): [width, height]
    describeShape(PossibleShapes.Triangle) // Triangle has 3 side(s): [base, height, hypotenuse]
}
  • In the above code:
    • The PossibleShapes enum has three values, each one has a number of sides and a list of side names.
    • The describeShape function takes a PossibleShapes as a parameter, and uses the when expression to check the type of the shape.

Notes

  • All text above is based on reading chapters 8, 12, and 16 from the book Elements of Kotlin by Mark L. Murphy.
  • The examples above are based on the examples in the book, but they are unique as I have written them myself.
  • There is a lot to the topic and cant be squashed in a few hundred words, so I kept it short and simple.

References