先来看官网的定义:
密封类和枚举同作为一个值的列举功能,但是密封类的功能更加强大。体现在:
在 Kotlin 1.1 之前,子类必须嵌套在密封类声明的内部。从 1.1 开始,变更为直接子类可以嵌套在密封类内部,也可以在密封类外,但是必须在密封类的 $\color{red} {Red} $ 同一个文件下面,间接子类则可以放在任意一个地方。
先来看个简单的加载状态的密封类的声明
// 声明一个密封类 LoadState
sealed class LoadState {
abstract val stateMessage: String
}
/**
* 直接继承密封类 LoadState,可以嵌套在 LoadState 内部,也可以放在同文件下
* 由于没有引用其他数据,这里建议直接用单例,可以复用
*/
object LoadStart: LoadState() {
override val stateMessage: String
get() = "Start load"
}
class LoadSuccess<out T>(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 表达式时,对于密封类需要覆盖所有的子类,如果没有,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 分支,编译器会提示错误
}
}
https://www.kotlincn.net/docs/reference/sealed-classes.html
https://blog.kotlin-academy.com/enum-vs-sealed-class-which-one-to-choose-dc92ce7a4df5
https://carterchen247.medium.com/kotlin%E4%BD%BF%E7%94%A8%E5%BF%83%E5%BE%97-sealed-class-82eccf890ac0