0Pricing
Android Academy · 课时

数据类与密封类

使用数据类实现基于值的相等判断和 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 反馈 — 无需本地设置。

此课程中的所有课时

  1. 空值安全
  2. 类与对象
  3. 数据类与密封类
  4. 扩展函数与高阶函数
← 返回 Android Academy