密封类层次结构
本课属于 Coddy Kotlin 学习旅程的“面向对象编程”单元。第 34 课,共 57 课。
sealed 类或接口具有一组封闭的直接子类,这些子类都声明在同一个包和模块中。编译器知道它们中的每一个,因此适合表示形态数量固定的结果:
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")
}在 main 中:
println(parseAge("42"))
println(parseAge("abc"))
println(parseAge("200"))输出:
Success(value=42)
Failure(reason=not a number: abc)
Failure(reason=out of range: 200)因为集合是封闭的,对密封类型使用 when 时,无需 else 也能覆盖所有情况。如果之后有人添加了子类,那么每个这样的 when 都会无法编译,直到处理了这个新情况,因此不会遗漏任何情况:
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}"
}在 main 中:
println(describe(Success(7)))
println(describe(Failure("timeout")))输出:
ok: 7
error: timeout不带数据的情况是一个 data object:一个具有可读 toString() 的单一实例。它的 when 分支不需要 is,因为只有一个实例。其他情况可以是数据类、对象或普通类:
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}"
}在 main 中:
for (line in listOf("move 3", "hello", "quit")) {
val c = parse(line)
println("$c: ${run(c)}")
}输出:
Move(steps=3): moving 3
Say(text=hello): saying hello
Quit: bye枚举还是密封层次结构?枚举是一组固定的单例,它们都具有相同的属性。密封层次结构允许每种情况携带自己的数据,而且像 Move 这样的情况可以有多个具有不同值的实例:
enum class Light { RED, YELLOW, GREEN } // 结构相同,每种各有一个实例
sealed interface Payment // 每种情况都有自己的数据
data class Card(val number: String) : Payment
data class Transfer(val iban: String, val reference: String) : Payment
data object Cash : Payment挑战
简单机器人理解密封接口 Command 中定义的命令:Move(steps)、Turn(left)、Report 和 Invalid(text),这些命令已提供。完成 parse(line):move 3 会得到 Move(3),turn left 和 turn right 会得到 Turn,report 会得到 Report,其他任何输入,包括不带有效数字的移动命令,都会得到带有该行内容的 Invalid。然后,使用穷尽式 when 且不写 else,完成 Robot.execute(command)。机器人从 (0, 0) 开始,朝向 N(北方是 y + 1,东方是 x + 1)。它会返回 moved to (0, 3)、facing W、at (0, 3) facing W 或 ignored。
提供的代码会解析每一行,并输出 Move(steps=3) -> moved to (0, 3)。
你的代码要写在 Robot.kt、Parser.kt、Command.kt、Move.kt、Turn.kt、Report.kt 和 Invalid.kt 中。Main.kt 包含提供的输入/输出代码,不能编辑。
亲手试试
fun main() {
// 提供的输入/输出代码:保持原样。
val input = generateSequence(::readLine).toList()
val robot = Robot()
for (line in input) {
val command = parse(line)
println("$command -> ${robot.execute(command)}")
}
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Kotlin 在线编辑器