Kotlin Sealed Classes

Introduction

Sealed classes in Kotlin are a special type of class that allow you to represent restricted class hierarchies. When a value can have one of a limited set of types, you can use a sealed class to define it. Unlike regular classes, all subclasses of a sealed class are known at compile time, which makes it possible to perform exhaustive checks when dealing with them.

Defining a Sealed Class

To define a sealed class, use the sealed keyword before the class keyword. Sealed classes can have subclasses, but all subclasses must be defined in the same file as the sealed class.

Syntax

sealed class SealedClass {
    // Properties and methods
}

class SubClass1 : SealedClass() {
    // Properties and methods
}

class SubClass2 : SealedClass() {
    // Properties and methods
}

Example

fun main() {
    val shape1: Shape = Circle(5.0)
    val shape2: Shape = Rectangle(4.0, 6.0)

    describeShape(shape1)
    describeShape(shape2)
}

sealed class Shape {
    data class Circle(val radius: Double) : Shape()
    data class Rectangle(val width: Double, val height: Double) : Shape()
}

fun describeShape(shape: Shape) {
    when (shape) {
        is Shape.Circle -> println("Circle with radius ${shape.radius}")
        is Shape.Rectangle -> println("Rectangle with width ${shape.width} and height ${shape.height}")
    }
}

Explanation:

  • sealed class Shape { ... }: Defines a sealed class Shape with two subclasses Circle and Rectangle.
  • describeShape(shape: Shape) { ... }: Uses a when expression to perform exhaustive checks on the type of shape.

Output:

Circle with radius 5.0
Rectangle with width 4.0 and height 6.0

Benefits of Sealed Classes

Exhaustive when Expressions

Since all possible subclasses of a sealed class are known at compile time, when expressions can be exhaustive, ensuring that all cases are handled.

Type Safety

Sealed classes provide better type safety by restricting the hierarchy to a known set of subclasses.

Example Program with Sealed Classes

Here is an example program that demonstrates various aspects of sealed classes in Kotlin:

Defining a Sealed Class for Network Responses

fun main() {
    val success = NetworkResponse.Success("Data loaded successfully")
    val error = NetworkResponse.Error(404, "Not Found")
    val loading = NetworkResponse.Loading

    handleResponse(success)
    handleResponse(error)
    handleResponse(loading)
}

sealed class NetworkResponse {
    data class Success(val data: String) : NetworkResponse()
    data class Error(val errorCode: Int, val message: String) : NetworkResponse()
    object Loading : NetworkResponse()
}

fun handleResponse(response: NetworkResponse) {
    when (response) {
        is NetworkResponse.Success -> println("Success: ${response.data}")
        is NetworkResponse.Error -> println("Error ${response.errorCode}: ${response.message}")
        is NetworkResponse.Loading -> println("Loading...")
    }
}

Explanation:

  • sealed class NetworkResponse { ... }: Defines a sealed class NetworkResponse with three subclasses Success, Error, and Loading.
  • handleResponse(response: NetworkResponse) { ... }: Uses a when expression to handle different types of network responses.

Output:

Success: Data loaded successfully
Error 404: Not Found
Loading...

Using Sealed Classes for Algebraic Data Types

Sealed classes can also be used to represent algebraic data types, which are commonly used in functional programming.

fun main() {
    val number: Expression = Add(Number(3), Number(5))
    val result = evaluate(number)
    println("Result: $result")
}

sealed class Expression
data class Number(val value: Int) : Expression()
data class Add(val left: Expression, val right: Expression) : Expression()
data class Subtract(val left: Expression, val right: Expression) : Expression()

fun evaluate(expr: Expression): Int {
    return when (expr) {
        is Number -> expr.value
        is Add -> evaluate(expr.left) + evaluate(expr.right)
        is Subtract -> evaluate(expr.left) - evaluate(expr.right)
    }
}

Explanation:

  • sealed class Expression { ... }: Defines a sealed class Expression with three subclasses Number, Add, and Subtract.
  • evaluate(expr: Expression): Int { ... }: Uses a when expression to evaluate different types of expressions.

Output:

Result: 8

Extending Sealed Classes

Sealed classes can be extended within the same file where they are defined. This ensures that all possible subclasses are known at compile time.

Example

fun main() {
    val vehicle1: Vehicle = Car("Toyota")
    val vehicle2: Vehicle = Bike("Honda")

    describeVehicle(vehicle1)
    describeVehicle(vehicle2)
}

sealed class Vehicle {
    data class Car(val brand: String) : Vehicle()
    data class Bike(val brand: String) : Vehicle()
}

fun describeVehicle(vehicle: Vehicle) {
    when (vehicle) {
        is Vehicle.Car -> println("Car brand: ${vehicle.brand}")
        is Vehicle.Bike -> println("Bike brand: ${vehicle.brand}")
    }
}

Explanation:

  • sealed class Vehicle { ... }: Defines a sealed class Vehicle with two subclasses Car and Bike.
  • describeVehicle(vehicle: Vehicle) { ... }: Uses a when expression to handle different types of vehicles.

Output:

Car brand: Toyota
Bike brand: Honda

Conclusion

In this chapter, you learned about sealed classes in Kotlin, including how to define sealed classes, their benefits, and how to use them in various scenarios. Sealed classes provide a powerful way to represent restricted class hierarchies, enabling exhaustive when expressions and better type safety. Understanding and applying sealed classes is crucial for writing robust and maintainable Kotlin programs.

Leave a Comment

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

Scroll to Top