Sealed Classes
In this page:
sealed class ClassName
class CaseA(val data: Type) : ClassName()
object CaseB : ClassName()
val result = when (value) { // no else needed
is CaseA -> value.data
CaseB -> other
}
Declaring a Sealed Class
sealed class restricts inheritance to a known, closed set of subclasses, which lets the compiler reason completely about every possible subtype at compile time.
Example: Declaring a Sealed Class
sealed class Result
// Define a data class `Success`
data class Success(val data: String) : Result()
// Define a data class `Failure`
data class Failure(val error: String) : Result()
// Entry point: execution of the program starts here
fun main() {
// Declare a read-only variable `result` (a `Result`), set to `Success("Loaded")`
val result: Result = Success("Loaded")
// Print `result` to the console, with a trailing newline
println(result)
}
Login to try C/C++/Java/PHP code in the editor
Exhaustive when Without else
Because the compiler knows every subclass of a sealed class, a when expression covering all of them does not need an else branch and still compiles safely.
Example: Exhaustive when Without else
sealed class Result
// Define a data class `Success`
data class Success(val data: String) : Result()
// Define a data class `Failure`
data class Failure(val error: String) : Result()
// Define the function `describe` taking `result` and returning a String
fun describe(result: Result): String = when (result) {
is Success -> "Success: ${result.data}"
is Failure -> "Failure: ${result.error}"
}
// Entry point: execution of the program starts here
fun main() {
// Print `describe(Success("Data loaded"))` to the console, with a trailing newline
println(describe(Success("Data loaded")))
// Print `describe(Failure("Timeout"))` to the console, with a trailing newline
println(describe(Failure("Timeout")))
}
Login to try C/C++/Java/PHP code in the editor
Sealed Classes for Modeling States
Sealed classes are a great fit for representing a small, fixed set of states that each need to carry different data, such as loading states in an application.
Example: Sealed Classes for Modeling States
sealed class LoadState
// Define the singleton object `Loading`
object Loading : LoadState()
// Define a data class `Loaded`
data class Loaded(val items: Int) : LoadState()
// Define a data class `Error`
data class Error(val message: String) : LoadState()
// Define the function `render` taking `state` and returning a String
fun render(state: LoadState): String = when (state) {
is Loading -> "Loading..."
is Loaded -> "Loaded ${state.items} items"
is Error -> "Error: ${state.message}"
}
// Entry point: execution of the program starts here
fun main() {
// Print `render(Loading)` to the console, with a trailing newline
println(render(Loading))
// Print `render(Loaded(10))` to the console, with a trailing newline
println(render(Loaded(10)))
}
Login to try C/C++/Java/PHP code in the editor
Sealed Classes Catch Missed Cases
If a new subclass is later added to a sealed class, any exhaustive when expression elsewhere that handled every existing case will fail to compile until it handles the new one too, catching missed logic early.
Triangle subclass here, this when would fail to compile until a Triangle branch was added.Example: Sealed Classes Catch Missed Cases
sealed class Shape
// Define a data class `Circle`
data class Circle(val radius: Double) : Shape()
// Define a data class `Square`
data class Square(val side: Double) : Shape()
// Define the function `area` taking `shape` and returning a Double
fun area(shape: Shape): Double = when (shape) {
is Circle -> Math.PI * shape.radius * shape.radius
is Square -> shape.side * shape.side
}
// Entry point: execution of the program starts here
fun main() {
// Print "Circle area: ${area(Circle(2.0))}" to the console, with a trailing newline
println("Circle area: ${area(Circle(2.0))}")
// Print "Square area: ${area(Square(3.0))}" to the console, with a trailing newline
println("Square area: ${area(Square(3.0))}")
}
Login to try C/C++/Java/PHP code in the editor
- Forgetting a sealed class's subclasses must all live in the same module (and, before certain Kotlin versions, the same file/package), causing a compile error if placed elsewhere.
- Adding an unnecessary
elsebranch in awhenover a sealed class, hiding the compiler's ability to warn you when a new subtype is added later. - Confusing a sealed class with an enum; sealed subclasses can each carry their own different data, unlike a plain enum's fixed constant set.
sealed classrestricts which classes can subclass it, and all direct subclasses must be known to the compiler at compile time.- A
whenexpression over a sealed class's subtypes can be exhaustive without anelsebranch, since the compiler knows every possibility. - Sealed classes are ideal for representing a fixed set of related, but structurally different, states or results.
- Adding a new subclass later causes any exhaustive
whenblocks to fail to compile until they handle the new case too.
Chapter Quiz — Complete all 7 topics to unlock
0/7 topics done
Complete these topics first: