密封类与when表达式:穷尽性检查、智能类型转换与分支处理
聊到密封类,就绕不开 when 表达式。这两个东西放在一起,才真正发挥出 Kotlin 类型系统的威力。我个人觉得,这是 Kotlin 相比 Java 最让人舒服的设计之一。
when 表达式的穷尽性检查
先问一个问题:你写 when 的时候,有没有漏掉某个分支?
在 Java 里,switch 漏掉一个 case,编译器不会管你。运行时没匹配到,就抛异常。但在 Kotlin 里,如果你用密封类作为 when 的参数,编译器会强制你覆盖所有子类型。这就是「穷尽性检查」。
sealed class Result {
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
object Loading : Result()
}
fun handleResult(result: Result) {
when (result) {
is Result.Success -> println("成功: ${result.data}")
is Result.Error -> println("错误: ${result.message}")
is Result.Loading -> println("加载中...")
}
// 如果少写一个分支,编译器会报错
}
我在项目中遇到过好几次这样的场景:团队新同学加了一个新的密封类子类型,但忘了更新所有 when 分支。结果编译直接报红,根本跑不起来。这比运行时才发现问题,省了太多 debug 时间。
智能类型转换
密封类配合 when 的另一个爽点是智能类型转换。说白了,就是编译器帮你自动做了类型强转。
fun process(result: Result) {
when (result) {
is Result.Success -> {
// 这里 result 已经被智能转换为 Success
println("数据长度: ${result.data.length}")
}
is Result.Error -> {
// 这里 result 被转换为 Error
println("错误信息: ${result.message.uppercase()}")
}
is Result.Loading -> {
// 这里 result 就是 Loading 对象
println("请稍候...")
}
}
}
你想想看,在 Java 里你要写 ((Result.Success) result).data,又长又容易出错。Kotlin 直接帮你省了这一步。我记得刚开始用 Kotlin 时,还习惯性地想手动强转,后来发现完全没必要。
is 检查后的变量。但密封类因为子类型有限,转换起来更安全、更自然。
分支处理的最佳实践
光会用还不够,怎么写得优雅才是关键。我总结了几条实战经验。
1. 优先使用 when 而非 if-else 链
有些人习惯写 if (result is Success) ... else if (result is Error) ...。我个人建议,只要遇到密封类,一律用 when。为什么?因为 when 能利用穷尽性检查,而 if-else 不行。
// ❌ 不推荐
fun bad(result: Result) {
if (result is Result.Success) {
// ...
} else if (result is Result.Error) {
// ...
} else {
// 这里如果忘了处理 Loading,编译器不会报错
}
}
// ✅ 推荐
fun good(result: Result) {
when (result) {
is Result.Success -> { /* ... */ }
is Result.Error -> { /* ... */ }
is Result.Loading -> { /* ... */ }
}
}
2. 用 else 分支兜底?尽量别用
有些同学喜欢在 when 最后加一个 else 分支。但如果你用了密封类,加 else 反而会破坏穷尽性检查。我曾经在代码 review 时看到有人写了 else -> {},结果后来新增了一个子类型,编译器没报错,线上出了 bug。
when 里,尽量不要写 else 分支。让编译器帮你兜底,而不是自己写一个空实现。
3. 分支逻辑复杂时,提取成独立函数
如果某个分支的处理逻辑超过 5 行,我建议把它提取成一个单独的函数。这样 when 表达式保持清晰,每个分支只做一件事。
fun handleResult(result: Result) {
when (result) {
is Result.Success -> handleSuccess(result)
is Result.Error -> handleError(result)
is Result.Loading -> showLoading()
}
}
private fun handleSuccess(success: Result.Success) {
// 复杂的业务逻辑
val processed = processData(success.data)
saveToDatabase(processed)
updateUI(processed)
}
private fun handleError(error: Result.Error) {
// 错误处理逻辑
logError(error.message)
showErrorDialog(error.message)
}
private fun showLoading() {
// 加载状态处理
showProgressBar()
}
4. 利用 when 的返回值
when 在 Kotlin 里是表达式,不是语句。这意味着它可以有返回值。这个特性配合密封类特别实用。
fun getDisplayText(result: Result): String {
return when (result) {
is Result.Success -> "操作成功: ${result.data}"
is Result.Error -> "操作失败: ${result.message}"
is Result.Loading -> "正在处理..."
}
}
嗯,这里要注意:如果 when 作为表达式使用,穷尽性检查就更加重要了。因为如果漏掉分支,编译器会直接报错,而不是返回 null 或默认值。
总结一下
密封类 + when 表达式,说白了就是 Kotlin 给状态管理提供的一把利器。穷尽性检查让你少犯错,智能类型转换让你少写代码,分支处理的最佳实践让你的代码更干净。
我在多个项目里都用这套模式来管理 UI 状态、网络请求结果、用户操作事件等。效果非常好,代码的可维护性明显提升。如果你还没用起来,我建议下次重构时试试看。