理解Kotlin密封类Sealed

本文介绍了Kotlin中的密封类,一种限制继承和提供编译时安全性的特性。密封类不允许外部扩展,常用于有限的类层次结构,如枚举的替代。文中通过示例展示了如何创建和使用密封类,以及它们在when表达式中的作用,强调了它们在隐藏实现细节和控制继承方面的作用。
摘要由CSDN通过智能技术生成

密封类和接口表示受限制的类层次结构,提供对继承的更多控制。密封类的所有直接子类在编译时都是已知的。在定义密封类的模块和包之外不能出现其他子类。例如,第三方客户端不能在其代码中扩展您的密封类。因此,密封类的每个实例都有一个在编译该类时已知的有限集合中的类型。

参考官网:https://kotlinlang.org/docs/sealed-classes.html

如果您像我一样,第一次阅读时可能无法理解这一点,特别是在没有实际代码示例的情况下。

1、密封类的特性

  • 密封类是抽象的,可以有抽象成员。

  • 密封类不能直接实例化。

  • 密封类不能有公共构造函数(默认情况下构造函数是私有的)。

  • 密封类可以有子类,但它们必须在同一个文件中,或者嵌套在密封类声明中。

  • 密封类的子类可以在密封类文件之外拥有子类。

2、Kotlin中的密封类:基本示例

下面是一个的密封类的基本示例:

sealed class Animal

上面代码的重要部分是“密封”修饰符。正如在密封类规则中提到的,密封类可以有子类,但它们必须都在同一个文件中,或者嵌套在密封类声明中。但是,密封类的子类不必在同一个文件中。

// 子类嵌套在密封类
sealed class Animal {
    class Dog() : Animal()
    class Cat() : Animal()
}


sealed class Animal
//子类不嵌套在密封类中,但必须在同一个文件类中
class Dog() : Animal()
class Cat() : Animal()


// Animal.kt
sealed class Animal() {
    class Dog() : Animal()
    class Cat() : Animal()
    open class UnKnownAnimal() : Animal()
}
// OtherAnimal.kt
class Duck : Animal()//编译失败
class cow : Animal.UnKnownAnimal()//编译成功

使用密封类的主要好处是在when表达式中使用它们时发挥作用。如果可以验证语句是否涵盖所有情况,则不需要向语句添加else子句。但是,只有当您使用when作为表达式(使用结果)而不是作为语句时,这才有效。

//Animal.kt
sealed class Animal() {
    class Dog() : Animal()
    class Cat() : Animal()
}


//MainAnimal.kt
//编译失败,由于没有复关全部子类结果
fun getAnimalName(type:Animal):String{
    return when (type){
        is Animal.Cat -> "Cat"
    }
}

上面这段代码失败了,因为我们必须实现所有类型。抛出的错误是:

'when' expression must be exhaustive, add necessary 'is Dog' branch or 'else' branch instead

这意味着我们必须在when表达式中包含所有类型的Animal。

fun getAnimalName(type:Animal):String{
    return when (type){
        is Animal.Cat -> "Cat"
        is Animal.Dog -> "Dog"
    }
}

3、Kotlin中的密封类:应用形式

a、带继承的密封类

密封类可以有子类,这允许您创建更复杂的类层次结构。下面是一个密封类层次结构的例子,它表示一个成功的结果和各种类型的错误:

sealed class Result<out T> {
    data class Success<out T>(val data: T) : Result<T>()

    sealed class Error : Result<Nothing>() {
        data class NetworkError(val code: Int) : Error()
        object Timeout : Error()
        object Unknown : Error()
    }
}
b、具有泛型类型的密封类

还可以将泛型类型与密封类一起使用,以创建更灵活和可重用的代码。下面是一个用泛型表示UI状态的密封类层次结构的例子:

sealed class UIState<out T> {
    data class Success<out T>(val data: T) : UIState<T>()
    data class Error(val message: String) : UIState<Nothing>()
    object Loading : UIState<Nothing>()
}
c、带扩展函数的密封类

与枚举类似,也可以使用扩展函数向密封类添加功能。下面是一个扩展函数的例子,它将一个Result密封的类实例映射到UIState:

fun <T> Result<T>.toUIState(): UIState<T> {
    return when (this) {
        is Result.Success -> UIState.Success(data)
        is Result.Error.NetworkError -> UIState.Error("Network error: $code")
        is Result.Error.Timeout -> UIState.Error("Request timed out")
        is Result.Error.Unknown -> UIState.Error("Unknown error")
    }
}

4、思考

在面向对象编程中,有时候我们想要隐藏一些类的实现细节,只暴露其子类。

通常情况可以将基础类的构造函数声明为 protected 或 private 来实现。这样,只有子类才能调用基础类的构造函数来创建对象,外部类无法创建基础类的对象,从而隐藏了基础类的实现细节。

以下是一个示例,演示如何隐藏基础类只暴露其子类:

open class Animal protected constructor(val name: String) {
    // 隐藏构造函数,只能被子类调用

    open fun makeSound() {
        println("This animal makes a sound.")
    }
}

class Dog(name: String) : Animal(name) {
    override fun makeSound() {
        println("Woof!")
    }
}

class Cat(name: String) : Animal(name) {
    override fun makeSound() {
        println("Meow!")
    }
}

在上面的代码中,我们定义了一个名为 Animal 的基础类,并将其构造函数声明为 protected,这意味着只有 Animal 的子类才能访问该构造函数。我们还定义了两个子类 DogCat,它们继承自 Animal 类。注意到,它们都没有定义自己的构造函数,因此它们将使用从 Animal 类继承的构造函数来创建对象。

通过这种方式,我们可以在不暴露 Animal 类的实现细节的情况下,使用 DogCat 类来创建动物对象。例如:

val dog = Dog("Fido")
val cat = Cat("Whiskers")

在上面的代码中,我们使用 DogCat 类来创建 dogcat 对象,它们都是 Animal 类的子类,但是我们无法直接创建 Animal 对象。

需要注意的是,使用 protected 修饰构造函数的类可以被继承,这意味着在同一包内部可以创建子类;如果有这样的需求,不想要再让外部自定义子类只使用现有定义的子类,这种情况就无法满足了,这里我们就想到了密封类sealed

我们已经知道,在 Kotlin 中,可以使用 sealed 关键字定义一个密封类(sealed class),从而避免外部直接实例化该类。

密封类是一个抽象类,用于限制类的继承层级,只能在其内部定义子类。外部无法继承密封类,也无法直接实例化密封类,只能通过其子类来实例化。

以下是一个使用 sealed 关键字定义密封类的例子:

sealed class Animal {
    abstract fun makeSound()
}

class Dog : Animal() {
    override fun makeSound() {
        println("汪汪汪")
    }
}

class Cat : Animal() {
    override fun makeSound() {
        println("喵喵喵")
    }
}

在上面的例子中,Animal 是一个密封类,它的子类只能在 Animal 内部定义,而外部无法继承或直接实例化 Animal 类。我们定义了 DogCat 两个子类,用于表示狗和猫。

通过使用 sealed 关键字,我们可以限制类的继承层级,确保所有的子类都在密封类内部定义,从而避免了外部直接实例化该类的情况,也避免了外部去自定义扩展子类。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值