Kotlin sealed class 的使用

什么是 sealed class

先来看官网的定义:

  • 密封类用来表示受限的类继承结构:当一个值为有限几种的类型、而不能有任何其他类型时。在某种意义上,他们是枚举类的扩展:枚举类型的值集合也是受限的,但每个枚举常量只存在一个实例,而密封类的一个子类可以有可包含状态的多个实例

密封类和枚举同作为一个值的列举功能,但是密封类的功能更加强大。体现在:

  • 密封类的每个子类可以由多个对象,枚举常量就是枚举类型的一个对象
  • 密封类子类可以持有其他引用数据,枚举常量只能持有枚举类构造方法传入的值
  • 密封类可以自定义方法,枚举常量只能重写枚举类的方法

sealed class 的声明

在 Kotlin 1.1 之前,子类必须嵌套在密封类声明的内部。从 1.1 开始,变更为直接子类必须在密封类的同一个文件下面,间接子类则可以放在任意一个地方。 先来看个简单的加载状态的密封类的声明

/**
 * 直接继承密封类 LoadState,可以嵌套在 LoadState 内部,也可以放在同文件下
 * 由于没有引用其他数据,这里建议直接用单例,可以复用
 */
object LoadStart: LoadState() {
    override val stateMessage: String
        get() = "Start load"
}

class LoadSuccess<out T>(private val result: T): LoadState() {
    override val stateMessage: String
        get() = "load success"
}

class LoadFail(private val throwable: Throwable): LoadState() {
    override val stateMessage: String
        get() = "load failed with exception : ${Log.getStackTraceString(throwable)}"
}
复制代码

如果用枚举去声明这个加载状态

enum class LoadState(val stateMessage: String) {
    LoadStart("Start load"),
    LoadSuccess("load success"),
    LoadFail("load failed")
}
复制代码

想要在枚举常量中的 LoadSuccess 和 LoadFail 中单独加入加载结果 result 和 Throwable 信息是不能够的。除非是在枚举类当中再加入 result 和 Throwable 变量,而 LoadSuccess 不需要 Throwable,LoadFail 不需要 result, LoadStart 更是都不需要,这就造成变量的冗余和资源的浪费。

enum class LoadState(val stateMessage: String) {
    LoadStart("Start load") {
        // LoadStart 不需要这两个字段
        override var result: Any
            get() = TODO("Not yet implemented")
            set(value) {}
        override var throwable: Throwable
            get() = TODO("Not yet implemented")
            set(value) {}
    },
    LoadSuccess("load success") {
        override var result: Any
            get() = TODO("Not yet implemented")
            set(value) {}
        // LoadSuccess 不需要这两个字段
        override var throwable: Throwable
            get() = TODO("Not yet implemented")
            set(value) {}
    },
    LoadFail("load failed") {
        // LoadFail 不需要这两个字段
        override var result: Any
            get() = TODO("Not yet implemented")
            set(value) {}
        override var throwable: Throwable
            get() = TODO("Not yet implemented")
            set(value) {}
    };
    
    abstract var result: Any
    
    abstract var throwable: Throwable
}
复制代码

when 表达式中的使用

当使用 when 表达式时,对于密封类需要覆盖所有的子类,如果没有,IDE 会提醒覆盖密封类的所有子类或者加入 else 语句,以达到逻辑的完整链路,这样在开发时就保证了程序的稳定性

fun testLoadState(loadState: LoadState) {
    val result = when (loadState) {
        LoadStart -> showLoadStartToast(loadState.stateMessage)
        is LoadSuccess<*> -> showResult(loadState.result)
        is LoadFail -> showLoadFailDialog(loadState.stateMessage)
    }
}
复制代码

这一点普通类是无法做到的,因为编译器不知道这个普通类到底有几个子类,一定要在最后加上一条 else 分支。假设 LoadState 是个普通抽象类

abstract class LoadState {
    abstract val stateMessage: String
}

fun testLoadState(loadState: LoadState) {
    val result = when (loadState) {
        LoadStart -> showLoadStartToast(loadState.stateMessage)
        is LoadSuccess<*> -> showResult(loadState.result)
        is LoadFail -> showLoadFailDialog(loadState.stateMessage)
        else -> {} // 不加入这条 else 分支,编译器会提示错误
    }
}
复制代码

总结

  • 如果当只需要一个常量集以区分不同类型,可以直接使用 enum class
  • 如果当需要一个 event,message 或者 state 类型,需要携带其他数据时,推荐使用 sealed class

参考文档

www.kotlincn.net/docs/refere…

blog.kotlin-academy.com/enum-vs-sea…

carterchen247.medium.com/kotlin%E4%B…

Guess you like

Origin juejin.im/post/7053014196648149028