使用sealed
关键字声明一个密封类或者接口
sealed interface Error
sealed class IOError(): Error
密封类和接口能够很好的控制继承,在密封类和接口定义的模块和包外无法被继承
在编译期我们就已知了所有的密封类和接口的实现类。在某种意义上,密封类类似于枚举类:枚举类型的值集也是受限制的,但枚举常量只存在为单个实例,而密封类的子类可以有多个实例,每个实例都有自己的状态。
举例来说,有个公共方法模块,定义了一个顶级错误密封类Error
,只要是模块中捕获了Error
的子类异常,就证明是当前模块抛出的错误,可以根据子类类型分别作出不同的处理。如果不是密封类,其他模块也继承了Error
类,因为不是在当前模块定义的,被当前模块捕获后没有相应的处理方式,可能导致运行异常。因为密封类的特性,所有子类型都是已知的,不会被其他模块继承,避免了上述未知异常
上述情况需要直接子类不能为
open
类型才能完美实现
package com.example
sealed interface Error
class CustomError() : Error
package io.example
import com.example.Error
class CustomError(): Error // 报错 -- Inheritor of sealed class or interface declared in package io.example but it must be in package com.example where base class is declared
注意上边例子的包名,在其他io包下继承密封类报错
密封类本身是抽象类,不能直接实例化,可以有抽象成员
密封类的构造函数可以是protected
或者private
,默认为protected
sealed class IOError {
constructor() { /*...*/ } // 默认protected
private constructor(description: String): this() { /*...*/ } // private
// public constructor(code: Int): this() {} // 报错 -- Error: public and internal are not allowed
}
直接子类位置(Location of direct subclasses)
- 直接子类必须在相同的包内声明。
- 子类可以是顶层类,也可以嵌套在任意数量的其他命名类、命名接口或命名对象内。
- 子类可以具有任何可见性,只要它们符合
kotlin
的正常继承规则。 - 密封类的子类必须具有正确的限定名称。
- 子类不能是局部的,也不能是匿名对象
枚举不能继承密封类,但是能实现密封接口
这些限制不适用于间接子类。如果密封类的直接子类没有标记为密封类,那么它可以根据其修饰符允许的任何方式进行扩展
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
多平台继承
在以后章节中讲解
密封类和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") }
// the `else` clause is not required because all the cases are covered
}
when expressions on expect sealed classes in the common code of multiplatform projects still require an else branch. This happens because subclasses of actual platform implementations aren’t known in the common code.