Kotlin Vocabulary | 密封类 sealed class
我们经常需要在代码中声明一些有限集合,如: 网络请求可能为成功或失败;用户账户是高级用户或普通用户。
我们可以使用枚举来实现这类模型,但枚举自身存在许多限制。枚举类型的每个值只允许有一个实例,同时枚举也无法为每个类型添加额外信息,例如,您无法为枚举中的 "Error" 添加相关的 Exception 类型数据。
当然也可以使用一个抽象类然后让一些类继承它,这样就可以随意扩展,但这会失去枚举所带来的有限集合的优势。而 sealed class (本文下称 "密封类" ) 则同时包含了前面两者的优势 —— 抽象类表示的灵活性和枚举里集合的受限性。继续阅读接下来的内容可以帮助大家更加深入地了解密封类,您也可以点击观看下方视频:
腾讯视频链接
https://v.qq.com/x/page/b0942n1m6el.html
Bilibili 视频链接
https://www.bilibili.com/video/BV1Nk4y1o7p3/
密封类的基本使用
// Result.kt
sealed class Result<out T : Any> {
data class Success<out T : Any>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
}
Cannot access ‘<init>’: it is private in Result
Kotlin 对象 https://kotlinlang.org/docs/reference/object-declarations.html#object-declarations
忘记了一个分支?
when(result) {
is Result.Success -> { }
is Result.Error -> { }
}
sealed class Result<out T : Any> {
data class Success<out T : Any>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
object InProgress : Result<Nothing>()
}
val action = when(result) {
is Result.Success -> { }
is Result.Error -> { }
}
当表达式必须覆盖所有选项时,添加 "is inProgress" 或者 "else" 分支。
如果想要在使用 when 语句时获得相同的编译器提示,可以添加下面的扩展属性:
val <T> T.exhaustive: T
get() = this
这样一来,只要给 when 语句添加 ".exhaustive",如果有分支未被覆盖,编译器就会给出之前一样的错误。
when(result){
is Result.Success -> { }
is Result.Error -> { }
}.exhaustive
IDE 自动补全
由于一个密封类的所有子类型都是已知的,所以 IDE 可以帮我们补全 when 语句下的所有分支:
当涉及到一个层级复杂的密封类时,这个功能会显得更加好用,因为 IDE 依然可以识别所有的分支:
sealed class Result<out T : Any> {
data class Success<out T : Any>(val data: T) : Result<T>()
sealed class Error(val exception: Exception) : Result<Nothing>() {
class RecoverableError(exception: Exception) : Error(exception)
class NonRecoverableError(exception: Exception) : Error(exception)
}
object InProgress : Result<Nothing>()
}
不过这个功能无法用于抽象类,因为编译器并不知道继承的层级关系,所以 IDE 也就没办法自动生成分支。
工作原理
为何密封类会拥有这些特性?下面我们来看看反编译的 Java 代码都做了什么:
sealed class Result
data class Success(val data: Any) : Result()
data class Error(val exception: Exception) : Result()
@Metadata(
...
d2 = {"Lio/testapp/Result;", "T", "", "()V", "Error", "Success", "Lio/testapp/Result$Success;", "Lio/testapp/Result$Error;" ...}
)
public abstract class Result {
private Result() {
}
// $FF: synthetic method
public Result(DefaultConstructorMarker $constructor_marker) {
this();
}
}
密封类的元数据中保存了一个子类的列表,编译器可以在需要的地方使用这些信息。
一个私有的默认构造方法 一个合成构造方法,只有 Kotlin 编译器可以使用
public final class Success extends Result {
@NotNull
private final Object data
public Success(@NotNull Object data) {
Intrinsics.checkParameterIsNotNull(data, "data");
super((DefaultConstructorMarker)null);
this.data = data;
}
想了解更多 Android 内容?
在公众号首页发送关键词 "Android",获取相关历史技术文章;
在公众号首页发送关键词 "ADS",获取开发者峰会演讲中文字幕视频;
还有更多疑惑?欢迎点击菜单 "联系我们" 反馈您在开发过程中遇到的问题。
推荐阅读