Skip to content
DeveloperMemos

Sealed Classes in Kotlin

Kotlin, Sealed Classes1 min read

A sealed class is a special type of class in Kotlin that allows you to define a restricted hierarchy of related classes. Unlike regular classes, sealed classes cannot be instantiated directly. Instead, they are intended to be used as base classes for a set of related classes, which are typically defined as subclasses of the sealed class.

Sealed classes are useful when you want to represent a fixed set of types, where each type has a unique behavior. For example, consider a simple expression evaluator that supports only two types of expressions: literals and binary operations. You can use a sealed class to define these two types of expressions and ensure that no other types of expressions can be created.

1sealed class Expr {
2 class Literal(val value: Int) : Expr()
3 class BinOp(val left: Expr, val op: String, val right: Expr) : Expr()
4}

In the example above, the Expr class is declared as sealed using the sealed modifier. This means that only classes defined within the same file as Expr can inherit from it. The two subclasses of Expr, Literal and BinOp, are defined as inner classes of Expr.

How do Sealed Classes Work?

When you declare a sealed class, Kotlin automatically generates a set of subclasses for it. These subclasses are defined within the same file as the sealed class and must be declared as data or inner classes.

Each subclass of a sealed class is a final class, which means that it cannot be further subclassed. This restriction ensures that the set of subclasses is fixed and that no other classes can be added to the hierarchy at a later time.

1sealed class Expr {
2 data class Literal(val value: Int) : Expr()
3 data class BinOp(val left: Expr, val op: String, val right: Expr) : Expr()
4}

In the example above, Literal and BinOp are declared as data classes, which makes them eligible for pattern matching using when expressions. Pattern matching with sealed classes is very useful because it allows you to exhaustively match on all possible cases without needing a default case.

1fun eval(expr: Expr): Int = when (expr) {
2 is Expr.Literal -> expr.value
3 is Expr.BinOp -> {
4 val left = eval(expr.left)
5 val right = eval(expr.right)
6 when (expr.op) {
7 "+" -> left + right
8 "-" -> left - right
9 "*" -> left * right
10 "/" -> left / right
11 else -> throw IllegalArgumentException("Unknown operator: ${expr.op}")
12 }
13 }
14}

The eval function above demonstrates how pattern matching with sealed classes can be used to evaluate expressions. By handling each possible case explicitly, we ensure that the function is total and that it will not throw an exception at runtime.

Conclusion

Sealed classes are a powerful feature in Kotlin that allow you to define restricted hierarchies of related classes. They are useful when you need to represent a fixed set of types and ensure that no other types can be created. By using sealed classes, you can write more expressive and safer code that is easier to maintain and reason about.