数据类与密封类
使用数据类实现基于值的相等判断和 copy(),并使用密封类构建完备的类型层次结构和清晰的状态模型。
数据类与密封类 是 CoddyKit 上的免费 Android Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Android Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Android Academy 课程共包含 4 节课。
什么是数据类
Kotlin 中的数据类是一种主要用于保存数据的类。编译器会自动为您生成实用的方法:
equals()— 按值比较两个实例hashCode()— 根据属性生成一致的哈希值toString()— 易读的字符串表示copy()— 创建修改后的副本- 支持解构
声明数据类
在 class 前添加 data 关键字。所有属性都必须位于主构造函数中:
data class User(val id: Int, val name: String, val email: String)
fun main() {
val u1 = User(1, "Alice", "alice@example.com")
val u2 = User(1, "Alice", "alice@example.com")
println(u1) // User(id=1, name=Alice, email=alice@example.com)
println(u1 == u2) // true (value equality)
println(u1 === u2) // false (different objects)
}copy() — 修改一个字段
copy() 会创建一个新实例,并修改其中的某些属性。原实例不会被修改(不可变性):
data class Product(val name: String, val price: Double, val inStock: Boolean)
fun main() {
val original = Product("Laptop", 999.0, true)
// Only change the price; keep everything else
val onSale = original.copy(price = 799.0)
println(original) // Product(name=Laptop, price=999.0, inStock=true)
println(onSale) // Product(name=Laptop, price=799.0, inStock=true)
}解构声明
数据类支持解构——使用编译器生成的 componentN() 函数,将属性解包到变量中:
data class Point(val x: Int, val y: Int)
fun main() {
val point = Point(10, 20)
// Destructure into separate variables
val (x, y) = point
println("x=$x, y=$y") // x=10, y=20
// Also works in for loops with maps
val scores = mapOf("Alice" to 95, "Bob" to 88)
for ((name, score) in scores) {
println("$name: $score")
}
}什么是密封类?
密封类是一种受限的类层次结构——所有子类都必须定义在同一个文件中。这样,所有可能的类型在编译时就已确定。
您可以使用密封类表示有限的状态集合,例如网络结果、界面状态或导航事件。
密封类语法
可以直接在密封类内部声明子类,也可以在同一个文件中声明:
sealed class NetworkResult<out T> {
data class Success<T>(val data: T) : NetworkResult<T>()
data class Error(val message: String, val code: Int = 0) : NetworkResult<Nothing>()
object Loading : NetworkResult<Nothing>()
}
fun handle(result: NetworkResult<String>) {
when (result) {
is NetworkResult.Success -> println("Data: ${result.data}")
is NetworkResult.Error -> println("Error ${result.code}: ${result.message}")
NetworkResult.Loading -> println("Loading...")
}
}密封类与 when(穷尽分支)
密封类的强大之处在于:when 是穷尽的。编译器会强制您处理每个子类型,不会遗漏任何情况:
sealed class UiState {
object Loading : UiState()
data class Success(val message: String) : UiState()
data class Error(val error: String) : UiState()
}
fun render(state: UiState): String = when (state) {
is UiState.Loading -> "Showing spinner"
is UiState.Success -> "Show: ${state.message}"
is UiState.Error -> "Error: ${state.error}"
// Compiler error if you remove any branch!
}
fun main() {
println(render(UiState.Loading))
println(render(UiState.Success("Loaded!")))
}密封类与枚举的比较
两者都用于表示固定的一组值,但:
- 枚举——每个常量都是单个实例,每个变体不携带额外数据
- 密封类——每个变体都可以携带不同的数据,也可以是类或对象
当变体不需要数据时使用枚举;当变体需要不同的属性时使用密封类。
对象声明(单例)
object 会创建一个单例——只有一个实例,延迟创建且线程安全:
object AppConfig {
const val BASE_URL = "https://api.example.com"
const val TIMEOUT = 30L
fun buildUrl(path: String) = "$BASE_URL/$path"
}
fun main() {
println(AppConfig.BASE_URL) // https://api.example.com
println(AppConfig.buildUrl("users")) // https://api.example.com/users
}伴生对象
伴生对象位于类内部,并提供类级成员(类似于 Java 的 static)。它通常用于工厂方法:
class Token private constructor(val value: String) {
companion object {
fun create(raw: String): Token {
val cleaned = raw.trim()
require(cleaned.isNotEmpty()) { "Token must not be blank" }
return Token(cleaned)
}
}
}
fun main() {
val token = Token.create(" abc123 ")
println(token.value) // abc123
}实际应用中的数据类
将数据类与密封类结合起来,可以清晰地建模状态:
data class Post(val id: Int, val title: String, val body: String)
sealed class PostState {
object Loading : PostState()
data class Loaded(val posts: List<Post>) : PostState()
data class Failed(val reason: String) : PostState()
}
fun showState(state: PostState) = when (state) {
PostState.Loading -> println("Fetching posts...")
is PostState.Loaded -> println("${state.posts.size} posts loaded")
is PostState.Failed -> println("Failed: ${state.reason}")
}
fun main() {
showState(PostState.Loaded(listOf(
Post(1, "Hello", "World"),
Post(2, "Kotlin", "Rocks")
)))
}快速检查
以下哪项最准确地描述了密封类与普通抽象类的区别?
回顾:数据类与密封类
现在您已经掌握了两个强大的 Kotlin 工具:
- 数据类——基于值的相等性、
copy()、解构以及自动生成的toString() - 密封类——有限的类型层次结构、穷尽的
when,并且每个变体都可以携带自己的数据 - 对象——单例;伴生对象可用于工厂方法
这些模式结合起来,可以替代 Java 中的样板代码,并实现清晰、安全的状态建模。
常见问题解答
「数据类与密封类」课时是免费的吗?
是的 — 「数据类与密封类」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Android Academy 课程的其余内容,请升级到 CoddyKit PRO。 Android Academy 课程共包含 4 节课。
「数据类与密封类」这节课中我会学到什么?
使用数据类实现基于值的相等判断和 copy(),并使用密封类构建完备的类型层次结构和清晰的状态模型。 你通过在浏览器中直接运行的动手代码来练习 Android Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Android Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Android Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「数据类与密封类」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Android Academy 课中编写并运行代码吗?
能。每节 Android Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。