Kotlin 密封类的优点在哪?
2021年03月09日
kotlin 的官方文档中,对密封类(Sealed Class)的介绍如下
密封类用来表示受限的类继承结构:当一个值为有限几种的类型、而不能有任何其他类型时。
在某种意义上,他们是枚举类的扩展:枚举类型的值集合也是受限的,但每个枚举常量只存在一个实例,而密封类的一个子类可以有可包含状态的多个实例。
其实对密封类与枚举的区别已经表达的很明确了,对比来说就是:
枚举类每种类型只允许有一个实例,而密封类可以有多个
枚举所有常量值类型必须相同,而密封类可以是多种
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23enum class Color(val value: Int) {
RED(0), BLUE(1), GREEN(2);
}
sealed class Expr
data class Const(val number: Double) : Expr()
data class Text(val content: String) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
fun main() {
val red1 = Color.RED
val red2 = Color.RED
println("${red1 == red2}") // true
val const1 = Const(1.0)
val const2 = Const(2.0)
println("${const1 == const2}") // false
}
密封类的优点:
- 密封类拥有抽象类的灵活,子类可以是任意的类,数据类,对象,普通类,甚至密封类
- 密封类拥有枚举的限制,子类必须写在与密封类同一文件中(kotlin1.1 之前,必须嵌套在密封类声明的内部)
- 特别 · 当子类涵盖所有情况时,使用 when 表达式,不必添加 else 分支
1 | fun eval(expr: Expr): Double = when(expr) { |
应用场景
网络请求状态封装
1
2
3
4
5
6
7
8
9
10
11
12
13
14sealed class ResponseResult<out T> {
data class Success<out T>(val value: T) : ResponseResult<T>()
data class Failure(val throwable: Throwable?) : ResponseResult<Nothing>()
}
when (result) {
is ResponseResult.Failure -> {
// 进行失败提示
}
is ResponseResult.Success -> {
// 进行成功处理
}
}列表数据区分
1
2
3
4sealed class ListItem {
class Text(val title: String, val content: String) : ListItem()
class Image(val url: String) : ListItem()
}-
1
2
3
4
5
6
7
8
9
10
11
12
13sealed class UiOp {
object Show: UiOp()
object Hide: UiOp()
class TranslateX(val px: Float): UiOp()
class TranslateY(val px: Float): UiOp()
}
fun execute(view: View, op: UiOp) = when (op) {
UiOp.Show -> view.visibility = View.VISIBLE
UiOp.Hide -> view.visibility = View.GONE
is UiOp.TranslateX -> view.translationX = op.px
is UiOp.TranslateY -> view.translationY = op.px
}
原理
通过反编译代码,查看 Java 代码实际的实现
1 | // 源代码 |
可以看出 Const 被编译成了 final class,Expr 被编译成 abstract class,同时生成一个公有的构造方法
- 构造函数私有化,限制子类必须嵌套在Sealed Class中
- 生成一个公有的构造函数,在子类的构造函数中调用父类的构造函数