Sealed Hierarchies
Part of the Object Oriented Programming section of Coddy's Kotlin journey. Lesson 34 of 57.
A sealed class or interface has a closed set of direct subclasses, all declared in the same package and module. The compiler knows every one of them, which suits results that have a fixed number of shapes:
sealed interface Result
data class Success(val value: Int) : Result
data class Failure(val reason: String) : Result
fun parseAge(text: String): Result {
val n = text.toIntOrNull() ?: return Failure("not a number: $text")
return if (n in 0..150) Success(n) else Failure("out of range: $n")
}Inside main:
println(parseAge("42"))
println(parseAge("abc"))
println(parseAge("200"))Output:
Success(value=42)
Failure(reason=not a number: abc)
Failure(reason=out of range: 200)Because the set is closed, a when over a sealed type is exhaustive without else. If someone adds a subclass later, every such when stops compiling until it handles the new case, so no case is forgotten:
sealed interface Result
data class Success(val value: Int) : Result
data class Failure(val reason: String) : Result
fun describe(r: Result) = when (r) {
is Success -> "ok: ${r.value}"
is Failure -> "error: ${r.reason}"
}Inside main:
println(describe(Success(7)))
println(describe(Failure("timeout")))Output:
ok: 7
error: timeoutA case without data is a data object: a single instance with a readable toString(). Its when branch needs no is, because there is only one instance. The other cases can be data classes, objects or ordinary classes:
sealed interface Command
data object Quit : Command
data class Move(val steps: Int) : Command
data class Say(val text: String) : Command
fun parse(line: String): Command = when {
line == "quit" -> Quit
line.startsWith("move ") -> Move(line.drop(5).toInt())
else -> Say(line)
}
fun run(c: Command) = when (c) {
Quit -> "bye"
is Move -> "moving ${c.steps}"
is Say -> "saying ${c.text}"
}Inside main:
for (line in listOf("move 3", "hello", "quit")) {
val c = parse(line)
println("$c: ${run(c)}")
}Output:
Move(steps=3): moving 3
Say(text=hello): saying hello
Quit: byeEnum or sealed hierarchy? An enum is a fixed set of single instances that all have the same properties. A sealed hierarchy lets every case carry its own data, and a case such as Move can have many instances with different values:
enum class Light { RED, YELLOW, GREEN } // same shape, one instance each
sealed interface Payment // each case has its own data
data class Card(val number: String) : Payment
data class Transfer(val iban: String, val reference: String) : Payment
data object Cash : PaymentChallenge
EasyA robot understands the commands of the sealed interface Command: Move(steps), Turn(left), Report and Invalid(text), which are supplied. Complete parse(line): move 3 gives Move(3), turn left and turn right give Turn, report gives Report, and anything else, including a move without a valid number, gives Invalid with the line. Then complete Robot.execute(command) with an exhaustive when and no else. The robot starts at (0, 0) facing N (north is y + 1, east is x + 1). It returns moved to (0, 3), facing W, at (0, 3) facing W or ignored.
The supplied code parses each line and prints Move(steps=3) -> moved to (0, 3).
Your code goes in Robot.kt, Parser.kt, Command.kt, Move.kt, Turn.kt, Report.kt and Invalid.kt. Main.kt holds the supplied input/output code and cannot be edited.
Try it yourself
fun main() {
// Supplied input/output code: keep it as it is
val input = generateSequence(::readLine).toList()
val robot = Robot()
for (line in input) {
val command = parse(line)
println("$command -> ${robot.execute(command)}")
}
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Classes and Objects
Working With FilesDefining ClassesPrimary Constructorsinit Blocks and ValidationSecondary ConstructorsRecap - Library Card4Inheritance
Open Classes and SubclassesOverriding Memberssuper and ConstructorsAbstract ClassesRecap - Staff Directory7Special Classes
Data ClassesEnum ClassesSealed Hierarchiesequals, hashCode, toStringRecap - Order Tracker2Properties and Encapsulation
Custom AccessorsVisibility ModifiersPrivate Setterslazy and lateinitRecap - Bank Account5Interfaces
Declaring InterfacesDefault MethodsMultiple InterfacesInterfaces as TypesRecap - Payment Methods8Generics
Generic FunctionsGeneric ClassesType ConstraintsVariance With in and outRecap - Priority Queue11Project: Library Management
Books and MembersBorrowing BooksPractice on your own: Kotlin playground