密封类和接口表示受限制的类层次结构,这些层次结构提供了对继承的更多控制。密封类的所有直接子类在编译时都是已知的。在定义密封类的模块和包之外,不得出现其他子类。例如,第三方客户端无法在其代码中扩展密封类。因此,密封类的每个实例都具有有限集合中的一个类型,该类型在编译此类时是已知的。
这同样适用于密封接口及其实现:一旦编译了具有密封接口的模块,就不会出现新的实现。
从某种意义上说,密封类类似于 enum
类:枚举类型的值集也受到限制,但每个枚举常量仅作为 单个实例 存在,而密封类的子类可以有多个实例,每个实例都有自己的状态。
例如,考虑库的 API。它可能包含错误类,以便库用户处理它可能引发的错误。如果此类错误类的层次结构包括公共 API 中可见的接口或抽象类,则没有什么可以阻止在客户端代码中实现或扩展它们。但是,库不知道在其外部声明的错误,因此它无法使用自己的类一致地处理它们。通过密封的错误类层次结构,库作者可以确保他们知道所有可能的错误类型,并且以后不会出现其他错误类型。
要声明密封的类或接口,请在其名称前添加sealed
修饰符:
sealed interface Error
sealed class IOError(): Error
class FileReadError(val file: File): IOError()
class DatabaseError(val source: DataSource): IOError()
object RuntimeError : Error
一个密封类是自身抽象的,它不能直接实例化并可以有抽象(abstract
)成员。
密封类的构造函数可以具有以下两个 visibilities 之一:protected
(默认)或private
:
sealed class IOError {
constructor() { /*...*/ } // protected by default
private constructor(description: String): this() { /*...*/ } // private is OK
// public constructor(code: Int): this() {} // Error: public and internal are not allowed
}
密封类和接口的直接子类必须在同一包中声明。它们可以是顶级的,也可以嵌套在任意数量的其他命名类、命名接口或命名对象中。子类可以具有任何可见性,只要它们与 Kotlin 中的正常继承规则兼容即可。
密封类的子类必须具有适当的限定名称。 它们不能是本地对象,也不能是匿名对象。
enum
类不能扩展密封类(以及任何其他类),但它们可以实现密封接口。
这些限制不适用于间接子类。如果密封类的直接子类未标记为密封类,则可以在其修饰符允许的任何方式对其进行扩展:
sealed interface Error // has implementations only in same package and module
sealed class IOError(): Error // extended only in same package and module
open class CustomError(): Error // can be extended wherever it's visible
多平台项目中还有一个继承限制: 密封类的直接子类必须驻留在同一个源集中。它适用于没有 expect
和 actual
修饰符 的密封类。
如果密封类在公共源代码集中声明为expect
,并且在平台源代码集中具有actual
实现,则expect
和actual
版本在其源代码集中都可以有子类。此外,如果您使用 分层结构,则可以在expect
和actual
声明之间的任何源代码集中创建子类。
使用密封类的关键好处在于使用 when
表达式的时候。 如果能够验证语句覆盖了所有情况,就不需要为该语句再添加一个 else
子句了:
fun log(e: Error) = when(e) {
is FileReadError -> { println("Error while reading file ${e.file}") }
is DatabaseError -> { println("Error while reading from database ${e.source}") }
is RuntimeError -> { println("Runtime error") }
// 不再需要 `else` 子句,因为已经覆盖了所有的情况
}
在多平台项目的通用代码中,
expect
密封类上的when
表达式仍然需要一个else
分支。发生这种情况是因为actual
平台实现的子类在通用代码中是未知的。