Kotlin密封类

发布于:2025-07-21 ⋅ 阅读:(11) ⋅ 点赞:(0)

基本语法

要声明一个密封类(sealed class)或密封接口(sealed interface),我们只需要在类或接口前加上 sealed 修饰符:

sealed class CustomError 
sealed interface CustomErrors

解释代码:
密封类和密封接口的区别仅在于类与接口之间的差异,因此我们将继续以密封类为例进行说明。

一个密封类是抽象的,因此它不能被实例化。下面的代码会导致错误:

fun main() {
    // 密封类型不能实例化
    val customError = CustomError()
}

解释代码:
但是,当然,你可以扩展它。

就像普通类一样,密封类也可以声明构造函数,但密封类中的构造函数必须是私有的或受保护的:

sealed class CustomError {
    
    constructor(type: String) {} // 受保护的(默认)
    private constructor(type: String, code: Int) {} // 私有的
    public constructor() {} // 公共构造函数会导致错误
}

解释代码:
你还可以像普通类一样使用主构造函数:

// 主构造函数 
sealed class CustomError(type: String) 

密封类与枚举类的对比

理解密封类的一种很好的方法是将它与枚举类进行对比。基本上,密封类像枚举类,但它更灵活。以下是对比的例子:

enum class Staff(numberOfLessons: Int) {
    TEACHER(2), MANAGER("Manager is managing")
}

解释代码:
这是枚举类的例子,但枚举类中不能有不同类型的构造参数(例如整数和字符串)。然而,使用密封类时是可以的:

sealed class Staff {
    class Teacher(val numberOfLessons: Int) : Staff()
    class Manager(val Responsibility: String) : Staff()
    object Worker : Staff()
}

解释代码:
枚举常量只有一种类型,而密封类则提供了更多的实例和更大的灵活性。我们可以得出结论:枚举类用于表示一组固定的值,而密封类用于表示给定类的固定子类集合。

枚举类不能继承类或接口,而密封类则可以。例如:

open class Person {
    fun whoAmI(name: String): String {
        return "I am $name"
    }
}

sealed class Staff : Person() {
    class Teacher(val numberOfLessons: Int) : Staff()
    class Manager(val Responsibility: String) : Staff()
    object Worker : Staff()
}

fun main() {
    val worker = Staff.Worker
    println(worker.whoAmI("Worker"))
}

解释代码:
我们声明了一个简单的 Person 类,并定义了一个方法,然后扩展了密封类 Staff,这赋予了我们继承的能力。在 main 方法中,我们调用了 Person 类的方法。这样一来,我们就能继承 Person 类中的功能了。

另一方面,如果我们尝试使用枚举类来做相同的事情,会导致错误:

enum class Staff : Person() {
    //...//
}

密封类与 when 表达式

密封类通常与 when 表达式一起使用,因为每个类都被视为一个案例。以下是一个例子:

sealed class Staff {
    class Teacher(val numberOfLessons: Int) : Staff()
    class Manager(val Responsibility: String) : Staff()
    object Worker : Staff()
}

fun listTheTasks(staff: Staff) = when (staff) {
    is Staff.Teacher -> println("The teacher has ${staff.numberOfLessons} lessons today")
    is Staff.Manager -> println("The manager is doing ${staff.Responsibility} today")
    Staff.Worker -> println("Worker is fixing the projector for profs in CS, all respect to him.")
}

解释代码:
我们声明了一个密封类 Staff,其中包含两个类和一个对象。一个对象在没有状态的情况下更为合适。然后我们创建了一个函数 listTheTasks。请注意,在类的情况下,需要使用 is,而在对象的情况下不需要。由于我们处理了所有的案例,因此没有 else 分支。

让我们运行这个函数:

fun main() {
    val teacher = Staff.Teacher(3)
    val worker = Staff.Worker
    listTheTasks(teacher)
    listTheTasks(worker)
}

// 输出:
// The teacher has 3 lessons today
// Worker is fixing the projector for profs in CS, all respect to him.

解释代码:
如你所见,when 表达式可以非常方便地处理密封类的每个子类。

直接子类的位置

最后,重要的一点是,密封类和接口的直接子类必须声明在同一个包中。另一方面,这一点对间接子类没有要求。你可能会问,什么是直接子类和间接子类。让我们通过一个简单的例子来澄清:

open class B : A() // B 是 A 的直接子类
open class C : B() // C 是 A 的间接子类,B 的直接子类

解释代码:
如果父类与子类之间没有其他类,那么它就是直接子类。

官方文档中提到:“密封类的所有直接子类在编译时都是已知的。没有其他子类可以出现在模块/包之外。”

结论

在本节中,我们学习了密封类,正如名称所示,“sealed” 限制了类层次结构,在我们需要表示一组固定的子类时非常有用。

我们还发现,密封类像枚举类,但提供了更多的灵活性。我们还学习了如何与 when 表达式一起使用,并讨论了密封类的类层次结构的限制。


网站公告

今日签到

点亮在社区的每一天
去签到