Sealed Class — 概念、工作原理及应用

作者: IT Sectr 发布日期: 2026-05-26 阅读时间: 8 分钟

Sealed Class — Kotlin 中的一种特殊类类型,它将继承层次结构限制为一组固定的子类型。所有继承者都在同一文件中声明,编译器已知它们,这允许使用详尽的 when 块而无需强制性的 else 分支。根据 Kotlin Docs, 2026,密封类是表示有限层次结构(如状态、错误类型和 UI 事件)的关键机制。

要点

  • Sealed Class — 具有固定继承者集合的类,这些继承者在同一文件中声明。
  • Exhaustive when — 编译器检查所有子类型是否已处理,消除被遗忘的 else 分支。
  • Sealed interface — Kotlin 1.5+ 支持用于多重继承的密封接口。
  • 错误层次结构 — sealed class 是 Kotlin 中类型安全错误处理的标准方式。
  • 与枚举的区别 — sealed class 的每个继承者可以包含唯一状态和不同数量的字段。

什么是 Sealed Class?

Sealed Class(密封类)— 是 Kotlin 中用 sealed 修饰符标记的类。它定义了一个有限的类型层次结构:所有可能的继承者都列在同一个文件中,编译器知道每一个继承者。这使密封类不同于普通的开放类,后者的继承者可以在任何地方声明。

密封类的主要目的是 有限集合的类型安全表示。每个继承者可以有自己的数据结构,这使得密封类比枚举更灵活。在运行时,密封类是一个普通的抽象类,编译器仅在编译阶段施加限制。

密封类在 Android 架构中尤其有用:UI 状态、网络请求结果、类似于 Intent 的导航事件以及错误层次结构 — 都是典型的应用场景。

在编译时,密封类被优化为 when 表达式的跳转表,使其比 if-else 链更高效。结合 data class,每个继承者不仅可以包含状态,还可以包含方法,从而无需样板代码即可构建自文档化的领域模型。

密封类在移动应用程序中表示 有限状态机也非常有效。每个状态 — 具有唯一参数的独立继承者,状态之间的转换通过 when 表达式控制。编译器保证所有可能的状态都已处理,从而消除 UI 状态或业务逻辑更改时的运行时错误。

Sealed Class 与 Enum:主要区别

Kotlin 初学者经常将密封类与 枚举 混淆,因为两者都限制值集合。然而它们之间存在根本区别:枚举是同一类型的常量集合,而密封类是不同类型层次结构。

何时选择枚举

枚举 在所有变体都是没有额外结构的常量时是最优选择。例如,星期几、订单状态或无需参数的操作类型。每个枚举值都是一个具有固定名称的单例。

何时选择密封类

密封类 在每个变体有自己的数据时需要。例如,网络错误包含响应代码,解析错误包含详细信息,授权错误包含消息。密封类的每个继承者是具有唯一字段的独立类型。

kotlin
// 枚举 — 同一类型的所有变体
enum class Status { LOADING, SUCCESS, ERROR }

// 密封类 — 每个变体都有自己的数据
sealed class UiState<out T> {
    object Loading : UiState<Nothing>()
    data class Success<T>(val data: T) : UiState<T>()
    data class Error(val message: String) : UiState<Nothing>()
}

Sealed Interface 与 Sealed Class 对比

从 Kotlin 1.5 开始,可以声明 密封接口。这将密封概念扩展到接口:密封接口也具有固定的实现集合,但支持多重继承。

何时使用密封接口

密封接口 在继承者需要同时实现多个契约时很方便。例如,UI 事件可以同时是可点击的和可追踪的。使用密封类时,需要选择一个基类,而使用密封接口,继承者同时实现两者。

密封类的限制

密封类 是一个类,因此每个继承者只能有一个父类。密封接口解决了这个问题,但不能包含状态。它们之间的选择取决于任务:需要带字段的公共逻辑 — 用密封类,需要契约的灵活性 — 用密封接口。

kotlin
sealed interface ScreenEvent {
    data class Refresh(val force: Boolean) : ScreenEvent
    data class Navigate(val route: String) : ScreenEvent
    data class ShowError(val toast: String) : ScreenEvent
}

sealed interface AnalyticsEvent {
    val name: String
    val params: Map<String, Any>
}

// 继承者实现两个接口
data class LoginClicked(
    override val name: String = "login_click",
    override val params: Map<String, Any> = emptyMap()
) : ScreenEvent, AnalyticsEvent

移动开发中用于错误层次结构的 Sealed Class

密封类在移动开发中的主要应用之一 — 类型安全错误层次结构。与其抛掷不同类型的异常或使用通用的 Exception,密封类将所有可能的领域错误收集到一个类型中。

如何构建错误层次结构

创建一个 DomainError 密封类,并将所有故障类型作为继承者列出。每个继承者只包含对该错误类型有意义的数据。编译器保证在处理错误时您不会忘记任何变体。

示例:身份验证错误处理

考虑一个带授权功能的应用程序,其中可能发生不同的故障场景:密码错误、账户封锁、服务器问题。密封类将它们统一为支持全面处理的单一类型。

kotlin
sealed class AuthError {
    data class InvalidCredentials(
        val attempts: Int
    ) : AuthError()

    data class AccountBlocked(
        val until: Long
    ) : AuthError()

    data class NetworkFailure(
        val cause: Throwable
    ) : AuthError()

    object ServerError : AuthError()
}

fun handleError(error: AuthError): String = when (error) {
    is AuthError.InvalidCredentials ->
        "剩余尝试次数: ${3 - error.attempts}"
    is AuthError.AccountBlocked ->
        "访问被锁定至 ${Date(error.until)}"
    is AuthError.NetworkFailure ->
        "检查连接: ${error.cause.localizedMessage}"
    AuthError.ServerError ->
        "服务器暂时不可用"
}

Android 中 Sealed Class 的使用模式

密封类 已成为 Android 应用程序架构中的标准工具。让我们看看移动开发中密封类不可或缺的三个关键模式。

  • UI State — 屏幕作为有限状态机的表示:Loading、Content、Error。每个状态包含自己的数据,密封类保证所有转换都已处理。
  • Navigation Event — 密封类代替导航常量:每个屏幕是具有路由参数的独立继承者。编译器检查参数类型。
  • Action/Intent — 单向数据流模式使用密封类表示用户可以在屏幕上执行的所有操作。

特别值得一提的是密封类在 Clean Architecture 中的应用。每一层(data、domain、presentation)使用自己的错误类型的密封类,映射器将一个密封类转换为另一个。例如,数据层的 DataError 映射为业务逻辑的 DomainError,然后再映射为表示层的 UiState。这保持了应用程序所有级别的类型安全性,并保证没有错误会未经处理。

测试密封类层次结构

测试 密封类需要特殊方法,因为每个继承者都是具有自己状态的独立类型。建议编写遍历所有密封类继承者的参数化测试。这保证了 when 表达式覆盖所有变体,包括层次结构扩展时添加的新变体。

对于 UI 测试,密封类作为 UiState 允许检查每个状态的显示:Loading 显示加载动画,Content 显示数据,Error 显示错误消息。由于密封类是有限的,所有状态的测试覆盖提供了对 UI 逻辑正确性的完全信心。

使用 Sealed Class 时的常见错误

尽管概念简单,开发人员在设计密封类层次结构时经常犯错。让我们看看主要问题及如何避免它们。

  • 继承者在不同文件中 — 如果继承者在文件外部,编译器将不允许声明密封类。这种限制保证了详尽的 when。
  • 混合 sealed 和 open — 密封类不能同时是 open。如果需要可扩展的层次结构,使用普通的抽象类,但会牺牲穷举性。
  • 过度嵌套 — 密封类中的密封类创建难以维护的深层层次结构。对于简单场景,两个级别就足够了。
  • when 中忘记 else — 如果库中的密封类不具有穷举性,编译器不会警告遗漏的分支。只有有意识地添加 else。

常见问题

密封类可以有抽象方法吗?

是的,密封类可以包含抽象方法,每个继承者必须实现它们。这在所有变体应提供统一接口但具有不同执行逻辑时很方便。

密封类在 Java 中可用吗?

Java 17+ 中出现了带有 sealed 修饰符的密封类和接口。Android 目前部分支持 Java 17,但在 Kotlin 项目中,密封类从 Kotlin 1.0 起可用且没有限制。

密封类可以继承另一个密封类吗?

是的,一个密封类可以是另一个的继承者。密封类层次结构保持有限:编译器知道每一层的所有继承者。这允许构建详细的错误分类。

密封类会影响性能吗?

密封类在运行时不会产生额外开销。编译器将带有密封类的 when 表达式优化为跳转表(tableswitch),比 if-else 链更快。性能与枚举相同。

如何测试密封类层次结构?

密封类的每个继承者单独测试。由于密封类是有限的,可以编写遍历所有变体的参数化测试。这提供了 when 块分支的完全覆盖。

总结

  • 密封类 — 具有固定继承者集合的类,在同一文件中声明,在编译时提供详尽的 when 分析。
  • 密封类的每个继承者可以有 自己的数据结构 — 与枚举的主要区别,枚举中所有变体都是同一类型的常量。
  • Sealed Interface(Kotlin 1.5+)支持多重继承,密封类仅支持单一继承。选择取决于对共享状态的需求。
  • 密封类 — Kotlin 中类型安全错误层次结构的 标准机制:每个故障类型是具有相关字段的独立继承者。
  • Android 中的主要模式:UI StateNavigation EventAction/Intent — 基于密封类构建以保证处理的完整性。
  • 避免 继承者在不同文件中、过度嵌套以及混合 sealed 与 open — 这违反了有限层次结构的契约。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读