深入学习 Kotlin 特色之 Sealed Class 和 Interface

本文从特点、场景和原理等角度综合分析了Kotlin语言的sealed class和sealed interface。sealed class可限制类的扩展,其子类有特定的定义规则;sealed interface和sealed class特点相近,还能帮助实现多继承和扩展性。文章还对比了它们与枚举、接口的区别。

🚨 前言

sealed class 以及 1.5 里新增的 sealed interface 可谓是 Kotlin 语言的一大特色,其在类型判断、扩展和实现的限制场景里非常好用。

本文将从特点、场景和原理等角度综合分析 sealed 语法。

  • Sealed Class
  • Sealed Interface
  • Sealed Class & Interface VS Enum
  • Sealed Class VS Interface

🏁 Sealed Class

sealed class,密封类。具备最重要的一个特点:

  • 其子类可以出现在定义 sealed class 的不同文件中,但不允许出现在与不同的 module 中,且需要保证 package 一致

这样既可以避免 sealed class 文件过于庞大,又可以确保第三方库无法扩展你定义的 sealed class,达到限制类的扩展目的。事实上在早期版本中,只允许在 sealed class 内部或定义的同文件内扩展子类,这些限制在 Kotlin 1.5 中被逐步放开。

如果在不同 module 或 package 中扩展子类的话,IDE 会显示如下的提示和编译错误:

Inheritor of sealed class or interface declared in package xxx but it must be in package xxx where base class is declared

sealed class 还具有如下特点或限制:

  1. sealed class 是抽象类,可以拥有抽象方法,无法直接实例化。否则,编译器将提示如下:

    Sealed types cannot be instantiated

  2. sealed class 的构造函数只能拥有两种可见性:默认情况下是 protected,还可以指定成 private,public 是不被允许的。

    Constructor must be private or protected in sealed class

  3. sealed class 子类可扩展局部以及匿名类以外的任意类型子类,包括普通 class、data classobject、sealed class 等,子类信息在编译期可知。

    假使匿名类扩展自 sealed class 的话,会弹出错误提示:

    This type is sealed, so it can be inherited by only its own nested classes or objects

  4. sealed class 的实例,可配合 when 表达式进行判断,当所有类型覆盖后可以省略 else 分支

    如果没有覆盖所有类型,也没有 else 统筹则会发生编译警告或错误

    1.7 以前:

    Non-exhaustive ‘when’ statements on sealed class/interface will be prohibited in 1.7.

    1.7 及以后:

    ‘when’ expression must be exhaustive, add …

当 sealed class 没有指定构造方法或定义任意属性的时候,建议子类定义成单例,因为即便实例化成多个实例,互相之间没有状态的区别:

‘sealed’ subclass has no state and no overridden ‘equals()’

下面结合代码看下 sealed class 的使用和原理:

示例代码:

// TestSealed.kt
sealed class GameAction(times: Int) {
   
   
    // Inner of Sealed Class
    object Start : GameAction(1)
    data class AutoTick(val time: Int) : GameAction(2)
    class Exit : GameAction(3)
}

除了在 sealed class 内嵌套子类外,还可以在外部扩展子类:

// TestSealed.kt
sealed class GameAction(times: Int) {
   
   
    ...
}

// Outer of Sealed Class
object Restart : GameAction(4)

除了可以在同文件下 sealed class 外扩展子类外,还可以在同包名不同文件下扩展。

// TestExtendedSealedClass.kt
// Outer of Sealed Class file
class TestExtendedSealedClass: GameAction(5)

对于不同类型的扩展子类,when 表达式的判断亦不同:

  • 判断 sealed class 内部子类类型自然需要指定父类前缀
  • object class 的话可以直接进行实例判断,也可以用 is 关键字判断类型匹配
  • 普通 class 类型的话则必须加上 is 关键字
  • 判断 sealed class 外部子类类型自然无需指定前缀
class TestSealed {
   
   
    fun test(gameAction: GameAction) {
   
   
        when (gameAction) {
   
   
            GameAction.Start -> {
   
   }
            // is GameAction.Start -> {}
            is GameAction.AutoTick -> {
   
   }
            
<think>我们正在讨论Kotlin的`sealed`关键字。根据用户要求,我们需要解释其用途用法,并提供示例。引用[1]提到的是数据类(dataclass),但用户的问题是关于`sealed`关键字的,因此我们需要专注于`sealed`。首先,`sealed`关键字在Kotlin中用于声明一个密封类sealedclass)。密封类用于表示受限制的类层次结构,即一个类可以有有限的一组子类,且这些子类必须在与密封类相同的文件中声明(在Kotlin1.1之后,子类也可以出现在同一个编译单元的其他文件中,但通常还是限定在同一个文件中或通过嵌套的方式)。主要用途:1.限制继承:密封类允许你定义一组固定的子类,这样在`when`表达式中处理所有情况时,可以确保覆盖所有可能的情况(如果覆盖了所有子类,则不需要`else`分支)。2.增强类型安全:由于所有可能的子类都是已知的,编译器可以检查是否处理了所有情况。示例:假设我们有一个表示网络请求结果的密封类,它有两个子类:成功错误。使用密封类定义: ```kotlinsealed classResult<out T: Any> {dataclass Success<outT :Any>(val data: T) :Result<T>()data classError(valexception:Exception): Result<Nothing>() }```然后,我们可以使用`when`表达式来处理结果: ```kotlinfunhandleResult(result:Result<Data>){when (result){is Result.Success-> {//处理成功情况,可以访问result.dataprintln("Success: ${result.data}")}is Result.Error ->{//处理错误情况println("Error: ${result.exception}")}} }```由于`Result`是密封类,只有两个子类,所以`when`表达式覆盖了所有情况,不需要`else`分支。注意:在Kotlin1.0中,密封类的子类必须嵌套在密封类内部。在Kotlin1.1中,子类可以定义在密封类同一个文件的任何地方。另一个常见用途是表示状态,例如UI状态:```kotlinsealed classViewState {objectLoading :ViewState()data classContent(valitems: List<String>): ViewState()dataclass Error(val message: String) :ViewState() }```然后,在更新UI时: ```kotlinfunupdateUI(state:ViewState) {when(state) {isViewState.Loading-> showLoading()isViewState.Content ->showContent(state.items)is ViewState.Error-> showError(state.message)}} ```同样,由于是密封类,我们处理了所有可能的状态。总结: -密封类用于表示有限的类层次结构。-在`when`表达式中处理密封类的实例时,如果覆盖了所有子类,则不需要`else`分支。-密封类本身是抽象的,不能直接实例化。相关问题:1.密封类枚举类有什么区别?2.在Kotlin中,密封类与抽象类有何不同?3.如何为密封类添加新的子类?4.密封类Kotlin中有哪些实际应用场景?</think>### Kotlin `sealed`关键字的用途与用法 `sealed`关键字用于声明**密封类**(sealed class),这是Kotlin中一种特殊的抽象类,主要解决两个核心问题: #### 核心特性 1. **受限继承** 密封类的所有直接子类必须在**同一个文件**中声明(Kotlin 1.1+允许在同一个编译单元的其他文件中声明),形成封闭的类层次结构: ```kotlin sealed class Result<out T> { data class Success<T>(val data: T) : Result<T>() data class Error(val message: String) : Result<Nothing>() } ``` 2. **完备性检查** 配合`when`表达式时,编译器能验证是否覆盖所有子类(无需`else`分支): ```kotlin fun handle(result: Result<String>) = when(result) { is Result.Success -> println(result.data) is Result.Error -> println("Error: ${result.message}") // 无需else,编译器确认覆盖所有情况 } ``` #### 典型应用场景 1. **状态机建模**(如UI状态) ```kotlin sealed class LoginState { object Idle : LoginState() object Loading : LoginState() data class Success(val user: User) : LoginState() data class Failed(val error: Throwable) : LoginState() } ``` 2. **表达式树处理** ```kotlin sealed class Expr { data class Const(val num: Int) : Expr() data class Sum(val left: Expr, val right: Expr) : Expr() } fun eval(expr: Expr): Int = when(expr) { is Expr.Const -> expr.num is Expr.Sum -> eval(expr.left) + eval(expr.right) } ``` 3. **API响应封装** ```kotlin sealed class ApiResponse<out T> { data class Success<T>(val data: T) : ApiResponse<T>() data class Failure(val code: Int) : ApiResponse<Nothing>() } ``` #### 与枚举的区别 | 特性 | 密封类 | 枚举类 | |--------------|---------------------------|---------------------| | 实例差异 | 子类可携带不同数据(如`data class`) | 所有实例类型相同 | | 状态数量 | 子类数量固定但实例数量不限 | 实例数量固定 | | 使用场景 | 需携带异构数据的受限类型 | 纯状态标识 | > 密封类本质是**抽象类**,不能直接实例化,其构造函数默认`protected`[^1]。 #### 版本演进 - **Kotlin 1.0**:子类必须嵌套在密封类内部 - **Kotlin 1.1+**:允许子类在**同一文件**的顶层声明 - **Kotlin 1.5+**:支持密封接口(`sealed interface`) --- ### 相关问题 1. 密封类与枚举类在状态管理中的具体选择标准是什么? 2. 如何在多文件项目中合理组织密封类及其子类? 3. 密封类在函数式编程中有哪些典型应用模式? 4. Kotlin密封接口sealed interface)解决了哪些密封类的限制? [^1]: 密封类通过受限继承保证类型安全,常用于替代枚举类扩展带数据的场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TechMerger

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值