Sealed Classes in Kotlin: Understanding Their Purpose, Benefits, and InternalWorkings

Amar Kumar
4 min readJul 10, 2024

--

What is a Sealed Class?

A sealed class in Kotlin is a type of class that limits which classes can inherit from it. Think of it like a club where only specific people are allowed to join. In Kotlin, you can define a sealed class and only certain types can be part of this “club.”

Why Do We Need Sealed Classes?

Sealed classes are handy when you have a limited set of options and you want to make sure that no other options can sneak in. This helps make your code safer and easier to manage. For example:

  • Representing different states of a process (like loading, success, and error).
  • Modeling different types of responses from a network request.
  • Providing a more flexible alternative to enums when you need to store additional data.

How to Use Sealed Classes

Using sealed classes is easy. Here’s a simple example:

sealed class NetworkResult {
data class Success(val data: String) : NetworkResult()
data class Error(val exception: Throwable) : NetworkResult()
object Loading : NetworkResult()
}
fun handleResult(result: NetworkResult) {
when (result) {
is NetworkResult.Success -> println("Data: ${result.data}")
is NetworkResult.Error -> println("Error: ${result.exception}")
NetworkResult.Loading -> println("Loading...")
}
}

In this example, NetworkResult is a sealed class with three possible outcomes: Success, Error, and Loading. The when statement is used to handle each possible type of NetworkResult, ensuring that we account for all possibilities.

Limitations of Sealed Classes

While sealed classes are useful, they do have some limitations:

  1. Same File Requirement: All subclasses of a sealed class must be in the same file. This can make the file large and harder to manage if there are many subclasses.
  2. No Flexibility for New Subclasses: Once you define a sealed class, you can’t add new subclasses outside the original file, which can be limiting.
  3. No Multiple Inheritance: Just like regular classes, sealed classes do not support inheriting from multiple classes.

Sealed Classes vs. Enums

Sealed classes and enums both represent a limited set of values, but they serve different purposes.

Enums:

  • Good for a fixed set of constants (like days of the week).
  • Simple and easy to use.
  • Can include properties and methods.

Sealed Classes:

  • More flexible.
  • Can hold different types of data.
  • Allows different implementations for each type.

Here’s a simple comparison:

enum class Color {
RED, GREEN, BLUE
}
sealed class Shape {
object Circle : Shape()
object Square : Shape()
data class Rectangle(val height: Int, val width: Int) : Shape()
}

Enums are great for straightforward, constant values. Sealed classes are better when you need more flexibility and complexity.

Internal Mechanics of Sealed Classes

When you define a sealed class in Kotlin, the compiler takes several steps to ensure its restricted nature. Let’s break down these steps:

  1. Abstract Base Class Generation: The sealed class itself is generated as an abstract class. This means it cannot be instantiated directly, and it can only be subclassed within the same file.
  2. Nested Class Creation: For each subclass of the sealed class, the compiler generates a static nested class. This nested class structure ensures that all possible subclasses are part of the sealed class’s hierarchy and are recognized by the compiler.
  3. Exhaustive when Expression Checks: When you use a when expression with a sealed class, the compiler can check that all possible subclasses are covered. This is because the compiler knows all the possible subclasses due to the restricted hierarchy.
  4. Compile-Time Safety: Since all subclasses are known at compile time, the compiler can ensure that you don’t miss any cases when handling the sealed class. This leads to safer and more reliable code.

Detailed Example and Bytecode Representation

Let’s revisit the NetworkResult example and dive deeper into the internal representation:

sealed class NetworkResult {
data class Success(val data: String) : NetworkResult()
data class Error(val exception: Throwable) : NetworkResult()
object Loading : NetworkResult()
}

When you compile this code, the Kotlin compiler generates an abstract base class NetworkResult and nested static classes for each subclass (Success, Error, and Loading).

Here’s a conceptual representation of what happens in bytecode:

public abstract class NetworkResult {
public static final class Success extends NetworkResult {
private final String data;
        public Success(String data) {
this.data = data;
}
public String getData() {
return data;
}
}
public static final class Error extends NetworkResult {
private final Throwable exception;
public Error(Throwable exception) {
this.exception = exception;
}
public Throwable getException() {
return exception;
}
}
public static final class Loading extends NetworkResult {
public static final Loading INSTANCE = new Loading();
private Loading() {
}
}
}

In this generated bytecode:

  • NetworkResult is an abstract class.
  • Success and Error are static nested classes with fields for their respective data (data and exception).
  • Loading is a singleton object represented as a static nested class with a single instance (INSTANCE)

Conclusion

Sealed classes in Kotlin offer a powerful way to manage restricted sets of types, making your code safer and easier to understand. They are particularly useful for modeling complex states and results, providing a flexible alternative to enums. Understanding how to use them, their benefits, and limitations can help you write better Kotlin code.

--

--