kotlin 的官方文档中,对密封类(Sealed Class)的介绍如下

密封类用来表示受限的类继承结构:当一个值为有限几种的类型、而不能有任何其他类型时。

在某种意义上,他们是枚举类的扩展:枚举类型的值集合也是受限的,但每个枚举常量只存在一个实例,而密封类的一个子类可以有可包含状态的多个实例。

其实对密封类与枚举的区别已经表达的很明确了,对比来说就是:

  1. 枚举类每种类型只允许有一个实例,而密封类可以有多个

  2. 枚举所有常量值类型必须相同,而密封类可以是多种

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    enum 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
2
3
4
5
6
fun eval(expr: Expr): Double = when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
// 不再需要 `else` 子句,因为我们已经覆盖了所有的情况
}

应用场景

  • 网络请求状态封装

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    sealed 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
    4
    sealed class ListItem {
    class Text(val title: String, val content: String) : ListItem()
    class Image(val url: String) : ListItem()
    }
  • 高级操作·UI 操作封装

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    sealed 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 源代码
sealed class Expr
object Const : Expr()

// 反编译后,省略部分代码
// 方法:Tools → Kotlin → Show Kotlin Bytecode -> DECOMPILE

// Expr.java
public abstract class Expr {
private Expr() {
}

public Expr(DefaultConstructorMarker $constructor_marker) {
this();
}
}
// Const.java

public final class Const extends Expr {
@NotNull
public static final Const INSTANCE;

private Const() {
super((DefaultConstructorMarker)null);
}
}

可以看出 Const 被编译成了 final class,Expr 被编译成 abstract class,同时生成一个公有的构造方法

  • 构造函数私有化,限制子类必须嵌套在Sealed Class
  • 生成一个公有的构造函数,在子类的构造函数中调用父类的构造函数

参考文献