FEATURED · 精选文章

Kotlin 数据类与密封类:从基础到实战

发布时间 / 2026/8/27 18:57:03
来源 / 创域科博编辑部
栏目 / 资讯中心
Kotlin 数据类与密封类:从基础到实战 1. 引言Kotlin 作为现代 JVM 语言在类型系统设计上提供了许多实用特性。其中数据类Data Class和密封类Sealed Class是日常开发中使用频率极高的两个概念。数据类帮助我们以极简的代码表达纯数据模型而密封类则为受限的类层次结构提供了优雅的解决方案。本文将通过丰富的代码实例深入讲解这两类特性的用法、原理与最佳实践。2. 数据类Data Class2.1 什么是数据类数据类是 Kotlin 中专门用于承载数据的类。使用data关键字修饰后编译器会自动生成equals()、hashCode()、toString()、copy()以及componentN()等常用方法大幅减少样板代码。data class User( val id: Long, val name: String, val email: String ) fun main() { val user User(1L, Alice, aliceexample.com) println(user) // User(id1, nameAlice, emailaliceexample.com) }2.2 自动生成的方法编译器为数据类自动生成的方法包括equals() / hashCode()基于主构造函数中的所有属性进行相等性比较。toString()输出格式为类名(属性值, ...)。copy()复制对象并允许修改部分属性。componentN()支持解构声明。data class Point(val x: Int, val y: Int) fun main() { val p1 Point(3, 4) val p2 p1.copy(y 10) // 复制并修改 y println(p2) // Point(x3, y10) // 解构声明 val (x, y) p1 println(x$x, y$y) // x3, y4 }2.3 数据类的约束条件使用数据类时需要注意以下约束主构造函数必须至少有一个参数。主构造函数的所有参数必须标记为val或var。数据类不能是抽象类、开放类、密封类或内部类。// 错误示例主构造函数没有参数 // data class Invalid // 正确示例主构造函数至少一个参数 data class Product(val sku: String, val price: Double)2.4 在类体中声明属性如果属性声明在数据类的类体中而非主构造函数中则不会参与自动生成的equals()、hashCode()和toString()。data class Order(val id: Long) { var status: String PENDING // 不参与 equals/hashCode/toString } fun main() { val o1 Order(1L).apply { status SHIPPED } val o2 Order(1L) println(o1 o2) // true因为 status 不参与比较 println(o1) // Order(id1) }3. 密封类Sealed Class3.1 什么是密封类密封类用于表达受限的类层次结构。使用sealed关键字修饰后其所有直接子类必须声明在同一个文件或同一个包中Kotlin 1.5 之后放宽为同一模块和包。密封类非常适合与when表达式配合使用实现穷尽性检查。sealed class Result { data class Success(val data: String) : Result() data class Error(val message: String) : Result() object Loading : Result() } fun handle(result: Result) { when (result) { is Result.Success - println(成功${result.data}) is Result.Error - println(失败${result.message}) Result.Loading - println(加载中...) // 无需 else 分支编译器知道所有子类 } }3.2 密封类与枚举的对比枚举适合表示固定数量的常量而密封类可以携带不同的状态和数据。当每个分支需要携带不同类型的数据时密封类是更优选择。// 枚举仅表示状态 enum class Status { SUCCESS, ERROR, LOADING } // 密封类每个分支可携带不同数据 sealed class UiState { data class Success(val data: ListString) : UiState() data class Error(val code: Int, val message: String) : UiState() object Loading : UiState() }3.3 密封类的递归结构密封类非常适合构建递归数据结构例如表达式树。sealed class Expr { data class Num(val value: Int) : Expr() data class Add(val left: Expr, val right: Expr) : Expr() data class Mul(val left: Expr, val right: Expr) : Expr() } fun eval(expr: Expr): Int when (expr) { is Expr.Num - expr.value is Expr.Add - eval(expr.left) eval(expr.right) is Expr.Mul - eval(expr.left) * eval(expr.right) } fun main() { // 表达式1 2 * 3 val expr Expr.Add(Expr.Num(1), Expr.Mul(Expr.Num(2), Expr.Num(3))) println(eval(expr)) // 7 }4. 数据类与密封类的组合实战4.1 网络请求状态管理在实际项目中数据类和密封类经常组合使用用于管理网络请求的多种状态。sealed class NetworkResultout T { data class Successout T(val data: T) : NetworkResultT() data class Failure(val code: Int, val message: String) : NetworkResultNothing() object Loading : NetworkResultNothing() } data class Article(val id: Long, val title: String, val content: String) fun fetchArticle(id: Long): NetworkResultArticle { return if (id 0) { NetworkResult.Success(Article(id, Kotlin 数据类, 正文内容...)) } else { NetworkResult.Failure(404, 文章不存在) } } fun render(result: NetworkResultArticle) { when (result) { is NetworkResult.Success - println(渲染文章${result.data.title}) is NetworkResult.Failure - println(错误 ${result.code}${result.message}) NetworkResult.Loading - println(显示加载动画) } }4.2 表单校验利用密封类表达校验结果可以让错误处理更加清晰。sealed class ValidationResult { object Valid : ValidationResult() data class Invalid(val field: String, val reason: String) : ValidationResult() } data class RegisterForm( val username: String, val password: String, val email: String ) fun validate(form: RegisterForm): ValidationResult { return when { form.username.length 3 - ValidationResult.Invalid(username, 用户名至少 3 个字符) form.password.length 6 - ValidationResult.Invalid(password, 密码至少 6 个字符) !form.email.contains() - ValidationResult.Invalid(email, 邮箱格式不正确) else - ValidationResult.Valid } } fun main() { val form RegisterForm(ab, 123, invalid-email) when (val result validate(form)) { ValidationResult.Valid - println(校验通过) is ValidationResult.Invalid - println(字段 ${result.field}${result.reason}) } }5. 最佳实践与注意事项5.1 数据类使用建议数据类应保持不可变性优先使用val声明属性。不要为数据类手动编写equals()、hashCode()、toString()除非有特殊需求。当属性较多时考虑使用copy()进行局部更新避免创建大量重复代码。5.2 密封类使用建议当分支数量固定且需要携带不同数据时优先选择密封类而非枚举。配合when表达式使用利用编译器的穷尽性检查避免遗漏分支。在 Kotlin 1.5 中密封类的子类可以分布在同一个模块的不同文件中但仍需在同一个包内。5.3 常见误区// 误区一在数据类中手动重写 toString 破坏自动生成 data class BadExample(val id: Int) { override fun toString(): String 自定义 // 不推荐除非有明确理由 } // 误区二密封类子类使用 var 破坏不可变性 sealed class State { data class Loading(var progress: Int) : State() // 尽量避免 var }6. 总结数据类和密封类是 Kotlin 类型系统中两个极具实用价值的特性。数据类通过自动生成样板方法让开发者专注于数据本身密封类则通过受限的类层次结构结合when表达式实现类型安全的穷尽分支处理。将两者组合使用可以构建出表达力强、易于维护的代码。建议读者在实际项目中多加练习逐步体会它们在状态管理、网络层封装、表单校验等场景中的优势。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻