Sealed Classes and Enums
|
This section documents Kotlin 2.4.x on the JVM, as published at kotlinlang.org, which is the reference these pages are written and verified against. This content was generated with the assistance of AI and should be verified against kotlinlang.org before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
sealed and enum both describe a closed set of possibilities, and both let a when expression over them be
checked for exhaustiveness at compile time — catching a forgotten case the moment a new variant is added rather
than at run time.
sealed class and sealed interface
A sealed type restricts its subtypes to those declared in the same module and package — the compiler
knows the complete, closed set of possibilities, unlike an ordinary open class or interface, whose subtypes
could be anywhere:
sealed interface PaymentResult
data class Approved(val confirmationCode: String) : PaymentResult
data class Declined(val reason: String) : PaymentResult
object Pending : PaymentResult // see Objects and Companion Objects
sealed subtypes are commonly data class`es carrying variant-specific data, plus `object for a variant with
no data at all — exactly the modeling job Java’s sealed + record combination was later added to cover, and
the shape most naturally suited to representing "one of several distinct outcomes" (an API response, a UI
state, a parse result).
Exhaustive when
A when expression over a sealed subject needs no else branch once every subtype is covered — the
compiler verifies exhaustiveness and raises a compile error (not just a warning) if a new subtype is added later
and a when site is not updated to handle it:
fun describe(result: PaymentResult): String = when (result) {
is Approved -> "approved: ${result.confirmationCode}" // smart-cast to Approved inside this branch
is Declined -> "declined: ${result.reason}"
Pending -> "still processing"
// no "else" needed -- and adding a new PaymentResult subtype later
// makes this "when" a compile error until it is updated
}
This exhaustiveness check is the payoff for sealing the hierarchy in the first place: it turns "did I forget to handle a case?" from a runtime bug into a compile-time one.
enum class
An enum class declares a fixed set of named instances, each of which can carry constructor arguments, its own
members, and even a constant-specific body overriding a member per-constant:
enum class Direction(val degrees: Int) {
NORTH(0), EAST(90), SOUTH(180), WEST(270); // note the semicolon before further members
fun opposite(): Direction = entries[(ordinal + 2) % entries.size]
}
enum class Operation {
PLUS {
override fun apply(a: Int, b: Int) = a + b // constant-specific body
},
TIMES {
override fun apply(a: Int, b: Int) = a * b
};
abstract fun apply(a: Int, b: Int): Int
}
println(Direction.NORTH.opposite()) // SOUTH
println(Operation.PLUS.apply(2, 3)) // 5
Direction.entries (the modern replacement for the older values()) returns an immutable List of every
constant in declaration order; .ordinal is its zero-based position and .name its declared identifier — both
inherited automatically, exactly like Java’s Enum. An enum class cannot be sealed and does not need to be:
it is already a closed set by construction, and when over one is exhaustive on the same terms as above.
See Also
-
Control Flow —
whenas an expression in general. -
Data Classes and Destructuring — the
data classvariants a sealed hierarchy is usually built from. -
Objects and Companion Objects — the
objectvariant with no data.