文章目录

Kotlin 密封类:sealed class 与 when 表达式

发布于 2026-04-11 03:14:06 · 浏览 8 次 · 评论 0 条

Kotlin 密封类:sealed class 与 when 表达式

Kotlin 中的 sealed class(密封类)是一种用于表示受限类继承层次结构的强大工具。它结合了枚举(enum)的类型安全性和抽象类的灵活性,非常适合处理状态管理、UI 渲染或结果传递等场景。配合 when 表达式使用时,编译器能够自动检查分支的完整性,从而避免潜在的逻辑漏洞。


1. 定义密封类结构

密封类的主要作用是限制继承。所有的子类必须声明在密封类所在的同一文件中(或在密封类的嵌套声明中)。这种约束使得编译器能够确定所有可能的子类类型。

创建一个新的 Kotlin 文件,例如 UiState.kt

输入以下代码定义一个基本的密封类层级结构:

// 定义基类,使用 sealed 关键字修饰
sealed class UiState

// 定义子类,继承自 UiState
data class Success(val data: String) : UiState()

// 定义子类,用于表示错误状态
data class Error(val exception: Exception) : UiState()

// 定义子类,用于表示加载中,使用 object 因为它不需要携带数据
object Loading : UiState()

注意:在上面的代码中,SuccessError 携带了不同的数据,而 Loading 是一个单例。这种混合使用 data classobject 的方式是密封类的一大优势。


2. 在 when 表达式中进行穷尽检查

使用普通类或接口配合 when 表达式时,通常需要 else 分支来处理未知情况。而使用密封类时,如果 when 分支覆盖了所有子类,编译器会认为该表达式是完整的,从而省略 else 分支。

编写一个处理 UI 状态的函数:

fun handleUiState(state: UiState) {
    // 直接使用 when 表达式
    val message = when (state) {
        // 编译器自动识别类型并进行智能转换
        is Success -> "数据加载成功: ${state.data}"
        is Error -> "发生错误: ${state.exception.message}"
        // 处理 object 对象
        Loading -> "正在加载中..."
        // 如果此处删除 Loading 分支,编译器会报错,提示并非所有分支都被覆盖
    }

    // 打印结果
    println(message)
}

观察代码逻辑:在 is Success 分支中,state 被自动转换为 Success 类型,因此可以直接访问 state.data 属性,无需额外的类型转换。由于 UiState 是密封的,且代码中列出了所有可能的子类(Success, Error, Loading),因此不需要编写 else 分支。


3. 对比不同方案的优劣

为了更清晰地理解密封类的适用场景,我们需要将其与常用的枚举类和普通抽象类进行对比。

特性 枚举 密封类 普通抽象类/接口
实例数量 每个常量是单例,数量有限 每个子类可创建多个实例 不限制
状态持有 每个实例只能有一组固定属性 每个子类可拥有不同类型和数量的属性 每个子类可拥有不同属性
继承层级 无层级结构(扁平化) 支持层级结构(可嵌套) 支持层级结构
when 表达式 支持穷尽检查(需导入或定义在同一处) 支持穷尽检查 不支持强制穷尽检查(需 else)

4. 构建复杂的层级结构

密封类支持嵌套定义,这允许构建更加复杂、逻辑分层的状态模型。

重构之前的代码,将网络状态独立封装:

sealed class NetworkResult

// 在 NetworkResult 内部嵌套定义 Success
sealed class Success : NetworkResult() {
    // 具体的成功状态,携带数据
    data class Data(val content: String) : Success()
    // 无数据的成功状态(例如 204 No Content)
    object NoContent : Success()
}

// 定义失败状态
data class Failure(val errorCode: Int, val message: String) : NetworkResult()

// 定义进行中状态
object InProgress : NetworkResult()

处理这种嵌套结构时,when 表达式依然能保持清晰的逻辑:

fun processResult(result: NetworkResult) {
    when (result) {
        // 注意:这里只需要匹配外层的具体类型
        is Success.Data -> println("处理数据: ${result.content}")
        is Success.NoContent -> println("操作成功,无返回内容")
        is Failure -> println("错误 ${result.errorCode}: ${result.message}")
        is InProgress -> println("请求发送中...")
        // 如果不处理 Success.NoContent,编译器会警告该分支未被覆盖
    }
}
```

为了直观展示这种继承关系,我们可以使用类图来描述:

```mermaid
classDiagram
    class NetworkResult {
        <<sealed>>
    }
    class Success {
        <<sealed>>
    }
    class Success_Data {
        +String content
    }
    class Success_NoContent {
        <<object>>
    }
    class Failure {
        +int errorCode
        +String message
    }
    class InProgress {
        <<object>>
    }

    NetworkResult <|-- Success
    NetworkResult <|-- Failure
    NetworkResult <|-- InProgress
    Success <|-- Success_Data
    Success <|-- Success_NoContent
```

---

### 5. 实战:处理 RecyclerView 的多种视图类型

在实际开发中,`RecyclerView` 经常需要展示不同类型的列表项。利用密封类可以优雅地封装每种视图类型对应的数据和逻辑。

**定义**视图数据的基类:

```kotlin
sealed class RecyclerViewItem

// 文本类型的数据
data class TextItem(val title: String, val description: String) : RecyclerViewItem()

// 图片类型的数据
data class ImageItem(val imageUrl: String, val caption: String) : RecyclerViewItem()

// 广告位的数据
data class AdItem(val adId: String, val sponsorName: String) : RecyclerViewItem()
```

**创建** ViewHolder 时的匹配逻辑:

```kotlin
fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
    // 实际开发中通常使用整数作为 viewType,这里演示逻辑映射
    return when (viewType) {
        TEXT_TYPE -> TextViewHolder(...)
        IMAGE_TYPE -> ImageViewHolder(...)
        AD_TYPE -> AdViewHolder(...)
        else -> throw IllegalArgumentException("未知的视图类型")
    }
}

fun onBindViewHolder(holder: RecyclerView.ViewHolder, item: RecyclerViewItem) {
    // 根据数据类型执行不同的绑定逻辑
    when (item) {
        is TextItem -> {
            // 智能转换 holder 为 TextViewHolder (前提是你有自定义 holder 类型检查或复用机制)
            // 这里演示数据处理的简便性
            println("绑定文本: ${item.title}")
        }
        is ImageItem -> {
            println("加载图片: ${item.imageUrl}")
        }
        is AdItem -> {
            println("展示广告: ${item.sponsorName}")
        }
    }
}

通过这种方式,将数据模型与渲染逻辑强关联,代码的可读性和维护性都得到了显著提升。当新增一种列表项类型时,只需继承 RecyclerViewItem 并在 when 表达式中补充对应的处理逻辑,编译器会自动引导你完成所有必须的修改。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文