[TOC]

密封类

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

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

要声明一个密封类,需要在类名前面添加 sealed 修饰符。

虽然密封类也可以有子类,但是所有子类都必须在与密封类自身相同的文件中声明。(在 Kotlin 1.1 之前, 该规则更加严格:子类必须嵌套在密封类声明的内部)。

要定义一个密封类,只需在定义类名前加上 sealed 关键字。比如对于上面的例子,可以这样定义 Expr 类:

1
2
3
4
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()

(上文示例使用了 Kotlin 1.1 的一个额外的新功能:数据类扩展包括密封类在内的其他类的可能性。

因为密封类是一个抽象类,所以不能用 data 等非抽象类的修饰符来修饰它,也不用加 open 关键字。

密封类的子类,要么写在密封类内部,要么写在父类同一个文件里,不能出现在其他地方。但子类的子类可以出现在其他地方。

密封类的特点:

1、一个密封类是自身抽象的,它不能直接实例化并可以有抽象(abstract)成员。所以密封类是为继承设计的,是一个抽象类;

2、密封类的子类是确定的,除了已经定义好的子类外,它不能再有其他子类。所以密封类不允许有非-private 构造函数(其构造函数默认为 private)。

现实世界里存在许多密封类的例子,比如根据年龄,可以把人(Person)分为成人(Adult)和儿童(Child);根据大小,把整数(Integer)分为正数(PositiveInteger)、负数(NegativeInteger)和零(Zero)。

可以发现,它们都属于 可以把元素地划分到几个确定的子类 的类,密封类就是专门为这种类提供的封装,可以看作封装类的枚举。

请注意,扩展密封类子类的类(间接继承者)可以放在任何位置,而无需在同一个文件中。

密封类的使用

使用密封类的关键好处在于使用 when 表达式 的时候,如果能够验证语句覆盖了所有情况,就不需要为该语句再添加一个 else 子句了。当然,这只有当你用 when 作为表达式(使用结果)而不是作为语句时才有用。

密封类的使用与一般抽象类并无不同,也就是说不能使用密封类实例化对象,只能用它的子类实例化对象。

密封类功能更多在于限制继承,起到划分子类的作用。将抽象类定义为密封类,可以 禁止外部继承,对于一些只划分为固定类型的数据,可以保证安全。

除此以外,密封类唯一的不同之处在于 when 语句对它有一个优化:因为密封类的子类型是确定的,所以在用 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` 子句,因为我们已经覆盖了所有的情况
}