Kotlin Sealed Classes

In this article, you will learn about Sealed class, how they are created, and when to use them with the help of examples.

Sealed classes are used when a value can have only one of the types from a limited set (restricted hierarchies).


Before going into details about sealed classes, let’s explore what problem they solve. Let’s take an example (taken from official Kotlin website – Sealed classes article):

class Expr
class Const(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr

fun eval(e: Expr): Int =
        when (e) {
            is Const -> e.value
            is Sum -> eval(e.right) + eval(e.left)
            else ->
                throw IllegalArgumentException("Unknown expression")
        }

In the above program, the base class Expr has two derived classes Const (represents a number) and Sum (represents sum of two expressions). Here, it’s mandatory to use else branch for default condition in when expression.

Now, if you derive a new subclass from Expr class, the compiler won’t detect anything as else branch handles it which can lead to bugs. It would have been better if the compiler issued an error when we added a new subclass.

To solve this problem, you can use sealed class. As mentioned, sealed class restricts the possibility of creating subclasses. And, when you handle all subclasses of a sealed class in an when expression, it’s not necessary to use else branch.


To create a sealed class, sealed modifier is used. For example,

sealed class Expr

Example: Sealed Class

Here’s how you can solve the above problem using sealed class:

sealed class Expr
class Const(val value: Int) : Expr()
class Sum(val left: Expr, val right: Expr) : Expr()
object NotANumber : Expr()


fun eval(e: Expr): Int =
        when (e) {
            is Const -> e.value
            is Sum -> eval(e.right) + eval(e.left)
            NotANumber -> java.lang.Double.NaN
        }

As you can see, there is no else branch. If you derive a new subclass from Expr class, the compiler will complain unless the subclass is handled in the when expression.


Few Important Notes

  • All subclasses of a sealed class must be declared in the same file where sealed class is declared.
  • A sealed class is abstract by itself, and you cannot instantiate objects from it.
  • You cannot create non-private constructors of a sealed class; their constructors are private by default.

Difference Between Enum and Sealed Class

Enum class and sealed class are pretty similar. The set of values for an enum type is also restricted like a sealed class.

The only difference is that, enum can have just a single instance, whereas a subclass of a sealed class can have multiple instances.

Kotlin Data Class

In this article, you will learn to create data classes in Kotlin. You will also learn about requirements that data class must fulfill, and their standard functionalities.

There may arise a situation where you need to create a class solely to hold data. In such cases, you can mark the class as data to create a data class. For example,

data class Person(val name: String, var age: Int)

For this class, the compiler automatically generates:

  • copy() function, equals() and hashCode() pair, and toString() form of the primary constructor
  • componentN() functions

Before talking about these features in detail, let’s talk about requirements that a data class must fulfill.


Kotlin Data Class Requirements

Here are the requirements:

  • The primary constructor must have at least one parameter.
  • The parameters of the primary constructor must be marked as either val (read-only) or var (read-write).
  • The class cannot be open, abstract, inner or sealed.
  • The class may extend other classes or implement interfaces. If you are using Kotlin version before 1.1, the class can only implement interfaces.

Example: Kotlin Data Class

data class User(val name: String, val age: Int)

fun main(args: Array<String>) {
    val jack = User("jack", 29)
    println("name = ${jack.name}")
    println("age = ${jack.age}")
}

When you run the program, the output will be:

name = jack
age = 29

When you declare a data class, the compiler automatically generates several functions such as toString()equals()hashcode() etc behind the scenes. This helps to keep you code concise. Had you used Java, you would need to write a lot of boilerplate code.

Let’s use these functions:


Copying

For a data class, you can create a copy of an object with some of its properties different using copy() function. Here’s how it works:

data class User(val name: String, val age: Int)

fun main(args: Array<String>) {
    val u1 = User("John", 29)
   
    // using copy function to create an object
    val u2 = u1.copy(name = "Randy")

    println("u1: name = ${u1.name}, name = ${u1.age}")
    println("u2: name = ${u2.name}, name = ${u2.age}")
}

When you run the program, the output will be:

u1: name = John, name = 29
u2: name = Randy, name = 29

toString() method

The toString() function returns a string representation of the object.

data class User(val name: String, val age: Int)

fun main(args: Array<String>) {
    val u1 = User("John", 29)
    println(u1.toString())
}

When you run the program, the output will be:

User(name=John, age=29)

hashCode() and equals()

The hasCode() method returns hash code for the object. If two objects are equal, hashCode() produces the same integer result.

The equals() returns true if two objects are equal (has same hashCode()). If objects are not equal, equals() returns false

data class User(val name: String, val age: Int)

fun main(args: Array<String>) {
    val u1 = User("John", 29)
    val u2 = u1.copy()
    val u3 = u1.copy(name = "Amanda")

    println("u1 hashcode = ${u1.hashCode()}")
    println("u2 hashcode = ${u2.hashCode()}")
    println("u3 hashcode = ${u3.hashCode()}")

    if (u1.equals(u2) == true)
        println("u1 is equal to u2.")
    else
        println("u1 is not equal to u2.")

    if (u1.equals(u3) == true)
        println("u1 is equal to u3.")
    else
        println("u1 is not equal to u3.")
}

When you run the program, the output will be:

u1 hashcode = 71750738
u2 hashcode = 71750738
u3 hashcode = 771732263
u1 is equal to u2.
u1 is not equal to u3.

Destructuring Declarations

You can destructure an object into a number of variables using destructing declaration. For example:

data class User(val name: String, val age: Int, val gender: String)

fun main(args: Array<String>) {
    val u1 = User("John", 29, "Male")

    val (name, age, gender) = u1
    println("name = $name")
    println("age = $age")
    println("gender = $gender")
}

When you run the program, the output will be:

name = John
age = 29
gender = Male

This was possible because the compiler generates componentN() functions all properties for a data class. For example:

data class User(val name: String, val age: Int, val gender: String)

fun main(args: Array<String>) {
    val u1 = User("John", 29, "Male")

    println(u1.component1())     // John
    println(u1.component2())     // 29  
    println(u1.component3())     // "Male"
}

When you run the program, the output will be:

John
29
Male

Kotlin Constructors

In this article, you will learn about constructors in Kotlin (both primary and secondary constructors) as well as initializer blocks with the help of examples.

A constructor is a concise way to initialize class properties.

It is a special member function that is called when an object is instantiated (created). However, how they work in Kotlin is slightly different.

In Kotlin, there are two constructors:

  • Primary constructor – concise way to initialize a class
  • Secondary constructor – allows you to put additional initialization logic

Primary Constructor

The primary constructor is part of the class header. Here’s an example:

class Person(val firstName: String, var age: Int) {
    // class body
}

The block of code surrounded by parentheses is the primary constructor: (val firstName: String, var age: Int).

The constructor declared two properties: firstName (read-only property as it’s declared using keyword val) and age (read-write property as it is declared with keyword var).


Example: Primary Constructor

fun main(args: Array<String>) {

    val person1 = Person("Joe", 25)

    println("First Name = ${person1.firstName}")
    println("Age = ${person1.age}")
}

class Person(val firstName: String, var age: Int) {

}

When you run the program, the output will be:

First Name = Joe
Age = 25

When the object of Person class is created, "Joe" and 25 values are passed as if Person is a function.

This initializes firstName and age properties of person1 object to "Joe" and 25 respectively.


There are other ways of using primary constructors.


Primary Constructor and Initializer Blocks

The primary constructor has a constrained syntax, and cannot contain any code.

To put the initilization code (not only code to initialize properties), initializer block is used. It is prefixed with init keyword. Let’s modify the above example with initializer block:

fun main(args: Array<String>) {
    val person1 = Person("joe", 25)
}

class Person(fName: String, personAge: Int) {
    val firstName: String
    var age: Int

    // initializer block
    init {
        firstName = fName.capitalize()
        age = personAge

        println("First Name = $firstName")
        println("Age = $age")
    }
}

When you run the program, the output will be:

First Name = Joe
Age = 25

Here, parameters fName and personAge inside the parenthesis accepts values "Joe" and 25 respectively when person1 object is created. However, fName and personAge are used without using var or val, and are not properties of the Person class.

The Person class has two properties firstName, and age are declared.

When person1 object is created, code inside initializer block is executed. The initializer block not only initializes its properties but also prints them.


Here is another way to perform the same task:

fun main(args: Array<String>) {
    val person1 = Person("joe", 25)
}

class Person(fName: String, personAge: Int) {
    val firstName = fName.capitalize()
    var age = personAge

    // initializer block
    init {
        println("First Name = $firstName")
        println("Age = $age")
    }
}

To distinguish the constructor parameter and property, different names are used (fName and firstName, and personAge and age). It’s more common to use _firstName and _age instead of completely different name for constructor parameters. For example:

class Person(_firstName: String, _age: Int) {
    val firstName = _firstName.capitalize()
    var age = _age

    // initializer block
    init {
        ... .. ...
    }
}

Default Value in Primary Constructor

You can provide default value to constructor parameters (similar to providing default arguments to functions). For example:

fun main(args: Array<String>) {

    println("person1 is instantiated")
    val person1 = Person("joe", 25)

    println("person2 is instantiated")
    val person2 = Person("Jack")

    println("person3 is instantiated")
    val person3 = Person()
}

class Person(_firstName: String = "UNKNOWN", _age: Int = 0) {
    val firstName = _firstName.capitalize()
    var age = _age

    // initializer block
    init {
        println("First Name = $firstName")
        println("Age = $age\n")
    }
}

When you run the program, the output will be:

First Name = Joe
Age = 25

person2 is instantiated
First Name = Jack
Age = 0

person3 is instantiated
First Name = UNKNOWN
Age = 0

Kotlin Secondary Constructor

In Kotlin, a class can also contain one or more secondary constructors. They are created using constructor keyword.

Secondary constructors are not that common in Kotlin. The most common use of secondary constructor comes up when you need to extend a class that provides multiple constructors that initialize the class in different ways. Be sure to check Kotlin Inheritance before you learn it.

Here’s how you can create a secondary constructor in Kotlin:

class Log {
    constructor(data: String) {
        // some code
    }
    constructor(data: String, numberOfData: Int) {
        // some code
    }
}

Here, the Log class has two secondary constructors, but no primary constructor.

You can extend the class as:

class Log {
    constructor(data: String) {
        // code
    }
    constructor(data: String, numberOfData: Int) {
        // code
    }
}

class AuthLog: Log {
    constructor(data: String): super(data) {
        // code
    }
    constructor(data: String, numberOfData: Int): super(data, numberOfData) {
        // code
    }
}

Here, constructors of the derived class AuthLog calls the corresponding constructor of the base class Log. For that, super() is used.

Calling constructor of base class from derived class.

In Kotlin, you can also call a constructor from another constructor of the same class (like in Java) using this().

class AuthLog: Log {
    constructor(data: String): this(data, 10) {
        // code
    }
    constructor(data: String, numberOfData: Int): super(data, numberOfData) {
        // code
    }
}
Calling constructor from the same class.

Example: Kotlin Secondary Constructor

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) {
    }
}

When you run the program, the output will be:

From AuthLog -> Bad Password: 10 times

Note: The secondary constructor must initialize the base class or delegate to another constructor (like in above example) if the class has no primary constructor.

Kotlin Class and Objects

In this article, you’ll be introduced to Object-oriented programming in Kotlin. You’ll learn what a class is, how to create objects and use it in your program.

Kotlin supports both functional and object-oriented programming.

Kotlin supports features such as higher-order functionsfunction types and lambdas which makes it a great choice for working in functional programming style. You will learn about these concept in later chapters. This article will focus on object-oriented style of programming in Kotlin.


Object-oriented Programming (OOP)

In object-oriented style of programming, you can divide a complex problem into smaller sets by creating objects.

These objects share two characteristics:

  • state
  • behavior

Let’s take few examples:

  1. Lamp is an object
    • It can be in on or off state.
    • You can turn on and turn off lamp (behavior).
  2. Bicycle is an object
    • It has current geartwo wheelsnumber of gear etc. states.
    • It has brakingacceleratingchanging gears etc. behavior.

You will learn about detail features of an object-oriented programming like: data encapsulationinheritance and polymorphism as we go on. This article will focus on the basics to keep things simple.


Kotlin Class

Before you create objects in Kotlin, you need to define a class.

A class is a blueprint for the object.

We can think of class as a sketch (prototype) of a house. It contains all the details about the floors, doors, windows etc. Based on these descriptions we build the house. House is the object.

Since, many houses can be made from the same description, we can create many objects from a class.


How to define a class in Kotlin?

To define a class in Kotlin, class keyword is used:

class ClassName {
    // property
    // member function
    ... .. ...
}

Here’s an example:

class Lamp {

    // property (data member)
    private var isOn: Boolean = false

    // member function
    fun turnOn() {
        isOn = true
    }

    // member function
    fun turnOff() {
        isOn = false
    }
}

Here, we defined a class named Lamp.

The class has one property isOn (defined in same way as variable), and two member functions turnOn() and turnOff().

In Kotlin, either the property must be initialized or must be declared abstract (Visit: Kotlin Abstract Class to learn more). In the above example, isOn property is initialized to false.


Classes, objects, properties, member function etc. can have visibility modifiers. For example, the isOn property is private. This means, the isOn property can be changed from only inside the Lamp class.

Other visibility modifiers are:

  • private – visible (can be accessed) from inside the class only.
  • public – visible everywhere.
  • protected – visible to the class and its subclass.
  • internal – any client inside the module can access them.

You will learn about protected and internal modifiers later in Kotlin visibility modifiers article.

If you do not specify the visibility modifier, it will be public by default.

In the above program, turnOn() and turnOff() member functions are public whereas, isOn property is private.


Kotlin Objects

When class is defined, only the specification for the object is defined; no memory or storage is allocated.

To access members defined within the class, you need to create objects. Let’s create objects of Lamp class.

class Lamp {

    // property (data member)
    private var isOn: Boolean = false

    // member function
    fun turnOn() {
        isOn = true
    }

    // member function
    fun turnOff() {
        isOn = false
    }
}

fun main(args: Array<String>) {

    val l1 = Lamp() // create l1 object of Lamp class
    val l2 = Lamp() // create l2 object of Lamp class
}

This program creates two objects l1 and l2 of class Lamp. The isOn property for both lamps l1 and l2 will be false.


How to access members?

You can access properties and member functions of a class by using . notation. For example,

l1.turnOn()

This statement calls turnOn() function for l1 object.

Let’s take another example:

l2.isOn = true

Here, we tried to assign true to isOn property of l2 object. Note that, isOn property is private, and if you try to access isOn from outside the class, an exception is thrown.


Example: Kotlin Class and Object

class Lamp {

    // property (data member)
    private var isOn: Boolean = false

    // member function
    fun turnOn() {
        isOn = true
    }

    // member function
    fun turnOff() {
        isOn = false
    }

    fun displayLightStatus(lamp: String) {
        if (isOn == true)
            println("$lamp lamp is on.")
        else
            println("$lamp lamp is off.")
    }
}

fun main(args: Array<String>) {

    val l1 = Lamp() // create l1 object of Lamp class
    val l2 = Lamp() // create l2 object of Lamp class

    l1.turnOn()
    l2.turnOff()

    l1.displayLightStatus("l1")
    l2.displayLightStatus("l2")
}

When you run the program, the output will be:

l1 Lamp is on.
l2 Lamp is off.

In the above program,

  • Lamp class is created.
  • The class has a property isOn and three member functions turnOn()turnOff() and displayLightStatus().
  • Two objects l1 and l2 of Lamp class are created in the main() function.
  • Here, turnOn() function is called using l1 object: l1.turnOn(). This method sets isOn instance variable of l1 object to true.
  • And, turnOff() function is called using l2 object: l1.turnOff(). This method sets isOff instance variable of l2 object to false.
  • Then, displayLightStatus() function is called for l1 and l2 objects which prints appropriate message depending on whether isOn property is true or false.

Notice that, the isOn property is initialized to false inside the class. When an object of the class is created, isOn property for the object is initialized to false automatically. So, it’s not necessary for l2 object to call turnOff() to set isOn property to false.

For example:

class Lamp {

    // property (data member)
    private var isOn: Boolean = false

    // member function
    fun turnOn() {
        isOn = true
    }

    // member function
    fun turnOff() {
        isOn = false
    }

    fun displayLightStatus() {
        if (isOn == true)
            println("lamp is on.")
        else
            println("lamp is off.")
    }
}

fun main(args: Array<String>) {

    val lamp = Lamp()
    lamp.displayLightStatus()
}

When you run the program, the output will be:

lamp is off.