ZetCode

Kotlin Sealed 关键字

最后修改于 2025 年 4 月 19 日

Kotlin 的 sealed 关键字创建受限类层次结构,其中所有子类都必须在同一文件中声明。 这支持详尽的 when 表达式并更好地控制继承。 本教程通过实际示例探讨了 sealed 类和接口。

基本定义

sealed 类默认是抽象的,不能直接实例化。 所有子类都必须在同一文件中声明。 sealed 接口的工作方式类似,但允许在不同的文件中实现。 编译器知道所有可能的子类型。

基本 Sealed 类

最简单的 sealed 类定义了一个受限层次结构,其中所有子类在编译时都是已知的。 这对于表示一组固定的相关类型很有用。

BasicSealed.kt
package com.zetcode

sealed class Result {
    data class Success(val data: String) : Result()
    data class Error(val message: String) : Result()
    object Loading : Result()
}

fun handleResult(result: Result) {
    when (result) {
        is Result.Success -> println("Success: ${result.data}")
        is Result.Error -> println("Error: ${result.message}")
        Result.Loading -> println("Loading...")
    }
}

fun main() {
    val success = Result.Success("Data loaded")
    handleResult(success) // Output: Success: Data loaded
}

在这里,我们定义了一个 sealed 类 Result,它有三种可能的状态。 when 表达式是详尽的,因为所有可能的子类都是已知的。 这确保我们处理所有情况,而无需 else 分支。

具有属性的 Sealed 类

Sealed 类可以具有所有子类继承的属性。 这允许共享公共数据,同时通过层次结构保持类型安全。

SealedWithProperties.kt
package com.zetcode

sealed class Vehicle(val wheels: Int) {
    class Car : Vehicle(4)
    class Bike : Vehicle(2)
    class Truck : Vehicle(18)
}

fun describeVehicle(vehicle: Vehicle) {
    println("This vehicle has ${vehicle.wheels} wheels")
}

fun main() {
    val car = Vehicle.Car()
    describeVehicle(car) // Output: This vehicle has 4 wheels
}

Vehicle sealed 类定义了一个公共 wheels 属性。 所有子类都继承此属性。 describeVehicle 函数可以安全地访问 wheels,因为知道所有 Vehicle 类型都具有此属性。

Sealed 接口

Kotlin 1.5 引入了 sealed 接口,它们的工作方式类似于 sealed 类,但允许在不同的文件中实现。 这提供了更大的灵活性。

SealedInterface.kt
package com.zetcode

sealed interface PaymentMethod {
    val accountId: String
}

data class CreditCard(override val accountId: String, val cardNumber: String) : PaymentMethod
data class BankTransfer(override val accountId: String, val bankCode: String) : PaymentMethod

fun processPayment(method: PaymentMethod) {
    println("Processing payment for account ${method.accountId}")
    when (method) {
        is CreditCard -> println("Using card ${method.cardNumber}")
        is BankTransfer -> println("Using bank code ${method.bankCode}")
    }
}

fun main() {
    val payment = CreditCard("acc123", "4111111111111111")
    processPayment(payment)
}

PaymentMethod sealed 接口定义了一个公共属性。 实现可以在不同的文件中。 when 表达式仍然是详尽的,因为编译器知道所有实现。 这将灵活性与类型安全相结合。

嵌套 Sealed 类

Sealed 类可以嵌套在其他类或接口中。 这有助于组织复杂的层次结构,同时保持 sealed 类的优势。

NestedSealed.kt
package com.zetcode

class ApiClient {
    sealed class Response {
        data class Success(val data: String) : Response()
        data class Error(val code: Int) : Response()
    }

    fun getResponse(): Response {
        return Response.Success("API data")
    }
}

fun main() {
    val client = ApiClient()
    val response = client.getResponse()
    
    when (response) {
        is ApiClient.Response.Success -> println(response.data)
        is ApiClient.Response.Error -> println("Error ${response.code}")
    }
}

Response sealed 类嵌套在 ApiClient 中。 这将相关类型保持在一起,同时保持 sealed 类的优势。 when 表达式仍然可以是详尽的,因为所有子类都是已知的。

具有函数的 Sealed 类

Sealed 类可以定义子类可以重写的函数。 这允许多态行为,同时保持受限层次结构。

SealedWithFunctions.kt
package com.zetcode

sealed class Shape {
    abstract fun area(): Double
    
    class Circle(val radius: Double) : Shape() {
        override fun area() = Math.PI * radius * radius
    }
    
    class Square(val side: Double) : Shape() {
        override fun area() = side * side
    }
}

fun printArea(shape: Shape) {
    println("Area: ${shape.area()}")
}

fun main() {
    val circle = Shape.Circle(5.0)
    printArea(circle) // Output: Area: 78.53981633974483
}

Shape sealed 类定义了一个抽象的 area 函数。 每个子类都提供自己的实现。 printArea 函数可以在任何 Shape 上调用 area(),知道所有子类都实现了它。 这将多态性与类型安全相结合。

详尽的 When 表达式

Sealed 类的主要好处之一是支持详尽的 when 表达式。 编译器确保处理所有情况,而无需 else 分支。

ExhaustiveWhen.kt
package com.zetcode

sealed class NetworkState {
    object Connected : NetworkState()
    object Disconnected : NetworkState()
    data class Error(val message: String) : NetworkState()
}

fun handleNetwork(state: NetworkState) {
    when (state) {
        NetworkState.Connected -> println("Connected")
        NetworkState.Disconnected -> println("Disconnected")
        is NetworkState.Error -> println("Error: ${state.message}")
    }
}

fun main() {
    val state = NetworkState.Connected
    handleNetwork(state) // Output: Connected
}

when 表达式不需要 else 分支,因为处理了所有可能的 NetworkState 子类。 如果我们稍后添加一个新的子类,编译器将标记未处理的情况。 这使代码更易于维护和更安全。

具有伴生对象的 Sealed 类

Sealed 类可以具有带有工厂方法的伴生对象。 这提供了一种干净的方式来创建实例,同时保持 sealed 层次结构。

SealedCompanion.kt
package com.zetcode

sealed class UserResult {
    data class Success(val user: String) : UserResult()
    data class Failure(val reason: String) : UserResult()
    
    companion object {
        fun success(user: String) = Success(user)
        fun failure(reason: String) = Failure(reason)
    }
}

fun main() {
    val result = UserResult.success("john_doe")
    
    when (result) {
        is UserResult.Success -> println("User: ${result.user}")
        is UserResult.Failure -> println("Failed: ${result.reason}")
    }
}

UserResult sealed 类在其伴生对象中提供了工厂方法。 这封装了子类的创建,同时保持了 sealed 层次结构的优势。 when 表达式仍然是详尽的,因为所有子类都是已知的。

Sealed 类的最佳实践

来源

Kotlin Sealed 类文档

本教程深入介绍了 Kotlin 的 sealed 关键字,展示了如何创建受限类层次结构。 我们探讨了 sealed 类、sealed 接口及其好处,例如详尽的 when 表达式。 正确使用 sealed 类型可以使您的代码更类型安全且更易于维护。

作者

我的名字是 Jan Bodnar,我是一位热情的程序员,拥有多年的编程经验。 自 2007 年以来,我一直在撰写编程文章。到目前为止,我撰写了 1400 多篇文章和 8 本电子书。 我有超过八年的编程教学经验。

列出 所有 Kotlin 教程