Skip to content

Latest commit

 

History

History
72 lines (46 loc) · 4.84 KB

密封类与密封接口.md

File metadata and controls

72 lines (46 loc) · 4.84 KB

密封类与密封接口

密封类和接口表示受限制的类层次结构,这些层次结构提供了对继承的更多控制。密封类的所有直接子类在编译时都是已知的。在定义密封类的模块和包之外,不得出现其他子类。例如,第三方客户端无法在其代码中扩展密封类。因此,密封类的每个实例都具有有限集合中的一个类型,该类型在编译此类时是已知的。

这同样适用于密封接口及其实现:一旦编译了具有密封接口的模块,就不会出现新的实现。

从某种意义上说,密封类类似于 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

多平台项目中的继承

多平台项目中还有一个继承限制: 密封类的直接子类必须驻留在同一个源集中。它适用于没有 expectactual 修饰符 的密封类。

如果密封类在公共源代码集中声明为expect,并且在平台源代码集中具有actual实现,则expectactual版本在其源代码集中都可以有子类。此外,如果您使用 分层结构,则可以在expectactual声明之间的任何源代码集中创建子类。

密封类和 when 表达式

使用密封类的关键好处在于使用 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平台实现的子类在通用代码中是未知的。