Skip to content

Kotlin sealed class and interface

1. Overview

In this article, we will discuss the Kotlin sealed class and the advantages of using it over an enum class.

2. Kotlin sealed class and interface

Let’s first understand the sealed class.

As the name suggests, Sealed classes and interfaces restrict the subclass hierarchies, meaning you would have more control over the inheritance.

Declaration of sealed interface and class:

sealed interface Expr
sealed class Job

Using sealed class, you can define the different subclasses which are known at compile time. You can’t add any new subclasses to it after you compile the module with the sealed class.

For example, you are sharing your code as a compiled jar file with your client with a sealed class in it. Your client can’t subclass any of your sealed classes in the jar.

A sealed class is an?abstract?class so you cannot create any instances directly or provide any implementations. But you can declare abstract?members.

sealed class MathExpr
fun main(args: Array)
{
    var obj = MathExpr()     //compiler error  
}

The same applies to sealed interfaces and their implementations. Once you compile a module with a sealed interface, no new implementations can appear. Sealed classes ensure compile-time checking by restricting the types to be matched rather than at runtime.

To declare a sealed interface or class, just specify the sealed keyword before the class or interface modifiers as mentioned above.

sealed interface Expr
sealed class MathExpr(): Expr
data class Const(val number: Double) : MathExpr()
data class Sum(val e1: Expr, val e2: Expr) : MathExpr()
object NotANumber : Expr

2.1. Sealed class Constructor

Constructors of sealed classes can have either protected or private visibility. By default, protected is used if there is no access modifier.

sealed class MathExpr {
    constructor() {  } // protected by default
    private constructor(vararg operands: Number): this() {  } // private is OK
    // public constructor(s: String): this() {} // Error: public and internal are not allowed
}

2.2. Kotlin sealed class vs enum

Similarities:

The set of values for an enum type is also restricted like the sealed class restricting the subclasses.

Enum and sealed class increase the compile-time checking by restricting the constants or types to be matched at compile-time instead of runtime check.

Differences:

Each enum constant exists only as a?single instance, whereas a subclass of a sealed class can have?multiple?instances, each with its own state. The state of an object is?stored in fields (Variables).

Let’s see examples to make it more sense. The value rgb is final and cannot change it at runtime once declared. So the state of an enum constant is always the same and also behaves as a single instance.

enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF)
}

The below sealed class has two subclasses Car and Bike. You can create any number of instances for each Car and Bike subclasses. Here, we created two instances for Car each having its own object state.

sealed class Vehicle
data class Car(val brandName: String, val owner: String, val color: String): Vehicle()
class Bike(val brandName: String, val owner: String, val color: String): Vehicle()
class Tractor(val brandName: String, val owner: String, val color: String): Vehicle()
val kiaCar = Car("KIA", "John", "Blue")
val hyundaiCar = Car("Hyundai", "Britto", "Green")

You can refer to our article on Enum to understand more.

2.3. Sealed modifier with interface errors

Suppose, you see any of the below errors while declaring sealed interface, then upgrade your Kotlin language version to 1.5 or above. Kotlin supports sealed interfaces only after version 1.5.

  • Modifier ‘sealed’ is not applicable to ‘interface’ Kotlin
  • The feature “sealed interfaces” is only available since language version 1.5

You can add this to your module’s build.gradle in Android to use Kotlin language 1.5 version or above. This will fix the error.

android {
    kotlinOptions {
        languageVersion = "1.5"
    }
}

2.4. Location of direct subclasses.

You should define all of your subclasses of the sealed class in the same package. However, it is not really required to define them within the sealed class, you can define them in any scope where the sealed class is visible. However, the subclass can have any visibility modifiers that adhere to Kotlin rules.

If you try to create a subclass outside the sealed class package, then the compiler would throw errors like “Inheritor of sealed class or interface declared in package com.tedblob but it must be in package com.tedblob.interfaces where the base class is declared“. Note this error may vary based on the platform you use.

Subclasses cannot be local or anonymous objects.

The below is an example to declare subclass inside the sealed class.

sealed class Vehicle {
    class Car(val brandName: String, val owner: String, val color: String): Vehicle() {
    }
}

2.5. Sealed classes and when expression.

Let’s talk about the use of sealed class in when expression. We can use when as an expression that returns a result and also as a statement.

In the below example, we used when as an expression that returns any of “this is a car”, “this is a tractor”, “this is a bike” string as result.

Since we know all the subclasses for a sealed class in advance, we can ignore an else clause in the when expression.

Here, we know that there are exactly three subclasses available for Vehicle sealed class. Since we are covering all the subclass conditions in when, there is no need to add an else clause here.

fun eval(vehicle: Vehicle): String {
    return when(vehicle) {
    is Car -> "This is a car"
    is Bike -> "This is a bike"
    is Tracktor -> "This is a tractor"
    // else case is not required as we have only two sub classes to Vehicle sealed class.
}

Suppose, you need to handle only two subclasses as shown below, then you had to add else clause. If no else clause is specified, then the compiler will throw the error ‘when’ expression must be exhaustive, add necessary ‘is Tractor’ branches or ‘else’ branch instead”.

fun eval(vehicle: Vehicle): String {
    return when(vehicle) {
    is Car -> "This is a car"
    is Bike -> "This is a bike"
    }
}

2.6. Sealed data class

Suppose you have a set of data subclass that share some common fields. So you had like to declare those in the sealed class which is the supertype to those data classes.

Below are the possible solutions for this approach.

sealed class Vehicle(open val regNumber: String)
data class Car(val name: String, override val regNumber: String): Vehicle(regNumber)

The above code will allocate storage for the regNumber variable twice. One for the superclass Vehicle and another for Car.

sealed class Message {
    abstract val messageId: String
}
data class Track(val event: String, override val messageId: String): Message()

3. Conclusion

In this article, we have learned the Kotlin sealed class along with few examples.

Leave a Reply

Your email address will not be published. Required fields are marked *