Kotlin – 19 – Sealed Classes in Kotlin

Sealed classes are a unique and powerful feature in Kotlin that allows you to define a restricted hierarchy of classes where each class represents a specific type or state. Sealed classes are particularly useful when you want to create a limited set of subclasses, often used for modeling finite states or as an alternative to enums. In this guide, we’ll explore sealed classes in Kotlin, their syntax, advantages, and practical use cases.

Defining a Sealed Class

To declare a sealed class in Kotlin, you use the sealed keyword followed by the class declaration. Inside a sealed class, you define a restricted set of subclasses. Here’s the basic syntax of a sealed class:

sealed class Result {
    data class Success(val data: String) : Result()
    data class Error(val message: String) : Result()
}

In this example, we’ve defined a sealed class Result with two subclasses, Success and Error. The sealed class restricts the set of subclasses to only those defined within it.

Restricted Hierarchy

Sealed classes enforce a restricted hierarchy, meaning that the subclasses of a sealed class must be declared within the same file where the sealed class is defined. This restriction ensures that the hierarchy is limited and well-defined.

Instances of Subclasses

Each subclass of a sealed class represents a specific type or state. In the example above, Success and Error are subclasses of Result, and each one carries specific data. You can create instances of these subclasses like regular classes:

val successResult = Result.Success("Data loaded successfully") val errorResult = Result.Error("Failed to load data")

Each instance carries its own data, making it easy to represent different outcomes or states in your code.

When Expressions with Sealed Classes

One of the most significant advantages of sealed classes is their compatibility with Kotlin’s when expressions. A when expression allows you to handle different cases or states explicitly, providing a clean and concise way to work with sealed class instances:

fun handleResult(result: Result) {
    when (result) {
        is Result.Success -> println("Success: ${result.data}")
        is Result.Error -> println("Error: ${result.message}")
    }
}

In this example, the handleResult() function takes a Result as an argument and uses a when expression to handle the different cases of Result. Because Result is a sealed class, the compiler ensures that all possible cases are covered, making your code more robust.

Advantages of Sealed Classes

Sealed classes offer several advantages in Kotlin:

  1. Restricted Hierarchy: Sealed classes restrict the set of possible subclasses, providing a clear and limited hierarchy of classes. This is particularly useful when modeling finite states or representing a closed set of options.
  2. Explicit Handling: When used in when expressions, sealed classes make it clear which cases must be handled. The compiler ensures that all possible cases are considered, reducing the likelihood of runtime errors.
  3. Data and State Modeling: Sealed classes are ideal for modeling data with different states or outcomes, such as network requests, validation results, or state machines.
  4. Code Readability: Sealed classes make code more self-documenting by explicitly defining the possible states or types an object can have.
  5. Pattern Matching: Sealed classes, in combination with when expressions, enable pattern matching, a powerful feature for working with complex data structures.
Practical Use Cases

Sealed classes are commonly used in various scenarios, including:

  • Network Responses: When making network requests, sealed classes can represent different response states like success, error, or loading.
  • Validation Results: Sealed classes can model the results of validation processes, distinguishing between valid and invalid states.
  • State Machines: Sealed classes can represent states and transitions in state machines, making the code more structured and readable.
  • Finite Sets of Options: When you have a fixed set of options or choices, sealed classes can represent each option as a subclass, ensuring that only valid choices are available.
Command and Example

Here’s a complete example demonstrating the use of sealed classes in Kotlin:

sealed class Result {
    data class Success(val data: String) : Result()
    data class Error(val message: String) : Result()
}

fun handleResult(result: Result) {
    when (result) {
        is Result.Success -> println("Success: ${result.data}")
        is Result.Error -> println("Error: ${result.message}")
    }
}

fun main() {
    val successResult = Result.Success("Data loaded successfully")
    val errorResult = Result.Error("Failed to load data")

    handleResult(successResult)
    handleResult(errorResult)
}

In this example, we’ve defined a sealed class Result with two subclasses, Success and Error. The handleResult() function uses a when expression to handle different cases of Result. In the main() function, we create instances of both Success and Error and pass them to handleResult(), demonstrating how sealed classes provide a structured and safe way to work with different states or outcomes.