Kotlin: Kotlin数据类与密封类详解

最后更新:2026-08-26

是 Kotlin 消灭样板代码的杀手锏——1 行代码自动生成 equals/hashCode/toString/copy/componentN。 则用有限的类型层次让编译器强制穷尽检查,Charlie 用它建模订单状态机零遗漏。

1. 你将学到


2. 一个开发者的真实故事

(1) 痛点:手写 equals 导致 Bug

Bob 在 Java 的 Order 类手写了 equals 方法,漏比较了 status 字段。结果 Set 去重时把不同状态的订单当作同一订单,导致 200 笔重复处理。

(2) data class 自动生成的解法

KOTLIN
// Java: 30 lines of error-prone equals/hashCode
// Kotlin: 1 line, compiler-generated and correct
data class Order(val id: String, val total: Double, val status: String)

// equals compares ALL constructor properties - can't forget a field

编译器自动生成基于所有构造器属性的 equals/hashCode,不可能漏字段。


3. data class 自动生成

(1) 基本用法

KOTLIN
data class Order(val id: String, val total: Double, val status: String)

val o1 = Order("ORD-001", 299.99, "CONFIRMED")
val o2 = Order("ORD-001", 299.99, "CONFIRMED")

// Auto-generated equals: compares all constructor properties
println(o1 == o2)  // true

// Auto-generated toString
println(o1)  // Order(id=ORD-001, total=299.99, status=CONFIRMED)

// Auto-generated copy: create modified instance
val updated = o1.copy(status = "SHIPPED")
println(updated)  // Order(id=ORD-001, total=299.99, status=SHIPPED)

// Auto-generated hashCode
println(o1.hashCode() == o2.hashCode())  // true

(2) 自动生成的 6 个方法

方法 功能 基于
值相等判断 所有构造器属性
哈希计算 所有构造器属性
字符串表示
创建修改副本 指定属性新值
解构声明 按属性声明顺序
解构声明 属性 N 对应 componentN

(3) copy 深度拷贝注意

KOTLIN
data class Order(val id: String, val items: MutableList`<String>`)

val o1 = Order("ORD-001", mutableListOf("Widget", "Gadget"))
val o2 = o1.copy()  // Shallow copy!

o2.items.add("Doohickey")
println(o1.items)  // [Widget, Gadget, Doohickey] - shared reference!

// Fix: explicitly copy the list
val o3 = o1.copy(items = o1.items.toMutableList())
⚠️ 注意: 是浅拷贝,引用类型属性仍然共享。需要深拷贝必须手动处理。


4. 解构声明

(1) 基本解构

KOTLIN
data class Order(val id: String, val total: Double, val status: String)

val order = Order("ORD-001", 299.99, "CONFIRMED")

// Destructuring declaration
val (id, total, status) = order
println("ID: $id, Total: \$$total USD, Status: $status")

// Partial destructuring with _
val (orderId, _, orderStatus) = order  // Skip total

// In for-loop
val orders = listOf(order, Order("ORD-002", 1_500.00, "PENDING"))
for ((oid, ototal, ostatus) in orders) {
    println("$oid: \$$ototal USD ($ostatus)")
}

(2) 解构应用场景

KOTLIN
// Map entries
val statusMap = mapOf("ORD-001" to "SHIPPED", "ORD-002" to "PENDING")
for ((id, status) in statusMap) { ... }

// Function return values
data class Result(val success: Boolean, val message: String)
val (ok, msg) = processOrder()

// Lambda parameters
orders.map { (id, total) -> "Order $id: \$$total USD" }

5. sealed class

(1) 密封类定义有限类型层次

KOTLIN
// Sealed class: restricted class hierarchy
// All subclasses MUST be in the same file (Kotlin 1.5: same package)
sealed class OrderStatus {
    object Pending : OrderStatus()
    data class Processing(val step: Int, val totalSteps: Int) : OrderStatus()
    object Shipped : OrderStatus()
    data class Delivered(val deliveredAt: String) : OrderStatus()
    data class Cancelled(val reason: String) : OrderStatus()
}

(2) 穷尽 when 分支

KOTLIN
fun handleStatus(status: OrderStatus): String = when (status) {
    is OrderStatus.Pending -> "Waiting for payment"
    is OrderStatus.Processing -> "Step ${status.step}/${status.totalSteps}"
    is OrderStatus.Shipped -> "In transit"
    is OrderStatus.Delivered -> "Delivered at ${status.deliveredAt}"
    is OrderStatus.Cancelled -> "Cancelled: ${status.reason}"
    // NO else needed - compiler verifies all cases covered
}

(3) 密封类状态机

100%
stateDiagram-v2
    [*] --> Pending
    Pending --> Processing : Pay
    Pending --> Cancelled : Cancel
    Processing --> Shipped : Ship
    Processing --> Cancelled : Fail
    Shipped --> Delivered : Deliver
    Delivered --> [*]
    Cancelled --> [*]

(4) sealed class vs enum class vs open class

维度
子类数量 固定枚举值 有限(编译期已知) 无限(运行时可扩展)
子类可持状态 单例 各自独立状态 各自独立状态
when 穷尽 ❌(需 else)
子类类型 同一类 不同类 不同类
扩展性 不可扩展 有限扩展 完全开放
适用场景 简单枚举 有限类型+状态 开放继承

6. Charlie 实战:订单事件溯源

KOTLIN
// Domain events for event sourcing
sealed class OrderEvent {
    data class Created(val orderId: String, val total: Double, val customer: String) : OrderEvent()
    data class PaymentReceived(val orderId: String, val amount: Double) : OrderEvent()
    data class ItemAdded(val orderId: String, val sku: String, val qty: Int) : OrderEvent()
    data class Shipped(val orderId: String, val trackingCode: String) : OrderEvent()
    data class Cancelled(val orderId: String, val reason: String) : OrderEvent()
}

data class OrderState(val id: String, val total: Double, val status: String, val items: List`<String>`)

fun applyEvent(state: OrderState?, event: OrderEvent): OrderState = when (event) {
    is OrderEvent.Created -> OrderState(event.orderId, event.total, "PENDING", emptyList())
    is OrderEvent.PaymentReceived -> state!!.copy(status = "PAID")
    is OrderEvent.ItemAdded -> state!!.copy(items = state.items + "${event.sku}x${event.qty}")
    is OrderEvent.Shipped -> state!!.copy(status = "SHIPPED")
    is OrderEvent.Cancelled -> state!!.copy(status = "CANCELLED")
}

7. 完整示例:OrderProcessor 状态机

KOTLIN
// ============================================
// OrderProcessor - State Machine with Sealed Classes
// Feature: Order lifecycle with exhaustive when
// ============================================

sealed class OrderStatus {
    object Pending : OrderStatus()
    data class Processing(val step: Int, val totalSteps: Int) : OrderStatus()
    object Shipped : OrderStatus()
    data class Delivered(val date: String) : OrderStatus()
    data class Cancelled(val reason: String, val refundAmount: Double) : OrderStatus()
}

data class Order(val id: String, val total: Double, var status: OrderStatus = OrderStatus.Pending)

fun describeStatus(status: OrderStatus): String = when (status) {
    is OrderStatus.Pending -> "Waiting for payment"
    is OrderStatus.Processing -> "Processing step ${status.step}/${status.totalSteps}"
    is OrderStatus.Shipped -> "In transit"
    is OrderStatus.Delivered -> "Delivered on ${status.date}"
    is OrderStatus.Cancelled -> "Cancelled: ${status.reason} (refund: \$${status.refundAmount} USD)"
}

fun canTransitionTo(current: OrderStatus, next: OrderStatus): Boolean = when {
    current is OrderStatus.Pending && next is OrderStatus.Processing -> true
    current is OrderStatus.Processing && next is OrderStatus.Shipped -> true
    current is OrderStatus.Shipped && next is OrderStatus.Delivered -> true
    current is OrderStatus.Pending && next is OrderStatus.Cancelled -> true
    current is OrderStatus.Processing && next is OrderStatus.Cancelled -> true
    else -> false
}

fun main() {
    val orders = listOf(
        Order("ORD-001", 299.99, OrderStatus.Pending),
        Order("ORD-002", 1_500.00, OrderStatus.Processing(2, 5)),
        Order("ORD-003", 8_900.00, OrderStatus.Shipped),
        Order("ORD-004", 45.50, OrderStatus.Delivered("2026-01-15")),
        Order("ORD-005", 2_500.00, OrderStatus.Cancelled("Customer request", 2_500.00))
    )

    println("=== Order Status Report ===")
    orders.forEach { order ->
        println("${order.id}: ${describeStatus(order.status)} (Total: \$${order.total} USD)")
    }

    // Test transitions
    val testOrder = Order("ORD-099", 100.00)
    println("\n=== Transition Tests ===")
    println("Pending -> Processing(1,3): ${canTransitionTo(testOrder.status, OrderStatus.Processing(1, 3))}")
    println("Pending -> Shipped: ${canTransitionTo(testOrder.status, OrderStatus.Shipped)}")
    println("Pending -> Cancelled: ${canTransitionTo(testOrder.status, OrderStatus.Cancelled("Error", 100.0))}")

    // Aggregate by status type
    val statusCounts = orders.groupBy { it.status::class.simpleName }
    println("\n=== Status Distribution ===")
    statusCounts.forEach { (status, list) -> println("  $status: ${list.size} orders") }
}

输出:

TEXT 📖 仅展示
=== Order Status Report ===
ORD-001: Waiting for payment (Total: $299.99 USD)
ORD-002: Processing step 2/5 (Total: $1500.0 USD)
ORD-003: In transit (Total: $8900.0 USD)
ORD-004: Delivered on 2026-01-15 (Total: $45.5 USD)
ORD-005: Cancelled: Customer request (refund: $2500.0 USD) (Total: $2500.0 USD)

=== Transition Tests ===
Pending -> Processing(1,3): true
Pending -> Shipped: false
Pending -> Cancelled: true

=== Status Distribution ===
  Pending: 1 orders
  Processing: 1 orders
  Shipped: 1 orders
  Delivered: 1 orders
  Cancelled: 1 orders

❓ 常见问题

Q data class 可以继承其他类吗?
A 可以继承非 data 类,但不能继承另一个 data 类。data class 可以实现接口。
Q data class 的 copy 是深拷贝吗?
A 不是。copy 是浅拷贝,引用类型属性仍共享同一对象。需要深拷贝必须手动处理。
Q sealed class 的子类必须在同一文件吗?
A Kotlin 1.5 之前必须在同一文件;1.5+ 只需在同一包内。这给了更大的组织灵活性。
Q sealed interface 和 sealed class 有什么区别?
A sealed interface(Kotlin 1.5+)允许类多实现,而 sealed class 只能单继承。sealed interface 更灵活。
Q 为什么密封类可以省略 when 的 else?
A 编译器知道密封类的所有子类,因此能验证 when 是否覆盖了所有情况。新增子类时编译器会报错,提醒你补充分支。
Q data class 的主构造器必须全是 val/var 吗?
A 不是,但只有 val/var 的属性才参与 equals/hashCode/copy/toString。普通参数不参与。

📖 小节


📝 作业

  1. 基础题(难度⭐):用 定义 ,测试 、 和解构声明。提示:
  2. 进阶题(难度⭐⭐):用 建模成功/失败,编写 处理两种情况。提示:
  3. 挑战题(难度⭐⭐⭐):用密封类建模完整的订单状态机(Pending→Processing→Shipped→Delivered / Cancelled),实现 函数,确保非法转换抛异常。提示: 穷尽检查 +

← 上一课 | 下一课 →

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏