使用sealed
关键字声明一个密封类或者接口
sealed interface Error
sealed class IOError(): Error
密封类和接口能够很好的控制继承,在密封类和接口定义的模块和包外无法被继承
在编译期我们就已知了所有的密封类和接口的实现类。在某种意义上,密封类类似于枚举类:枚举类型的值集也是受限制的,但枚举常量只存在为单个实例,而密封类的子类可以有多个实例,每个实例都有自己的状态。
举例来说,有个公共方法模块,定义了一个顶级错误密封类Error
,只要是模块中捕获了Error
的子类异常,就证明是当前模块抛出的错误,可以根据子类类型分别作出不同的处理。如果不是密封类,其他模块也继承了Error
类,因为不是在当前模块定义的,被当前模块捕获后没有相应的处理方式,可能导致运行异常。因为密封类的特性,所有子类型都是已知的,不会被其他模块继承,避免了上述未知异常
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
密封类本身是抽象类,不能直接实例化,可以有抽象成员
密封类的构造函数可以是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
}