Kotlin Vocabulary | 密封类 sealed class

咱们常常须要在代码中声明一些有限集合,如: 网络请求可能为成功或失败;用户帐户是高级用户或普通用户。html

咱们可使用枚举来实现这类模型,但枚举自身存在许多限制。枚举类型的每一个值只容许有一个实例,同时枚举也没法为每一个类型添加额外信息,例如,您没法为枚举中的 "Error" 添加相关的 Exception 类型数据。网络

固然也可使用一个抽象类而后让一些类继承它,这样就能够随意扩展,但这会失去枚举所带来的有限集合的优点。而 sealed class (本文下称 "密封类" ) 则同时包含了前面二者的优点 —— 抽象类表示的灵活性和枚举里集合的受限性。继续阅读接下来的内容能够帮助你们更加深刻地了解密封类,您也能够点击观看 视频app

密封类的基本使用

和抽象类相似,密封类可用于表示层级关系。子类能够是任意的类: 数据类、Kotlin 对象、普通的类,甚至也能够是另外一个密封类。但不一样于抽象类的是,您必须把层级声明在同一文件中,或者嵌套在类的内部。ide

// 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>()
}

尝试在密封类所定义的文件外继承类 (外部继承),则会致使编译错误:post

Cannot access ‘<init>’: it is private in Result

忘记了一个分支?

在 when 语句中,咱们经常须要处理全部可能的类型:this

when(result) {
   is Result.Success -> { }
   is Result.Error -> { }
}

可是若是有人为 Result 类添加了一个新的类型: InProgress:spa

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>()
}

若是想要防止遗漏对新类型的处理,并不必定须要依赖咱们本身去记忆或者使用 IDE 的搜索功能确认新添加的类型。使用 when 语句处理密封类时,若是没有覆盖全部状况,可让编译器给咱们一个错误提示。和 if 语句同样,when 语句在做为表达式使用时,会经过编译器报错来强制要求必须覆盖全部选项 (也就是说要穷举):code

val action = when(result) {
  is Result.Success -> { }
  is Result.Error -> { }
}

当表达式必须覆盖全部选项时,添加 "is inProgress" 或者 "else" 分支。视频

若是想要在使用 when 语句时得到相同的编译器提示,能够添加下面的扩展属性:htm

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();
   }
}

密封类的元数据中保存了一个子类的列表,编译器能够在须要的地方使用这些信息。

Result 是一个抽象类,而且包含两个构造方法:

  • 一个私有的默认构造方法
  • 一个合成构造方法,只有 Kotlin 编译器可使用

这意味着其余的类没法直接调用密封类的构造方法。若是咱们查看 Success 类反编译后的代码,能够看到它调用了 Result 的合成构造方法:

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;
   }
}

开始使用密封类来限制类的层级关系,让编译器和 IDE 帮忙避免类型错误吧。

相关文章
相关标签/搜索