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 幫忙避免類型錯誤吧。

相關文章
相關標籤/搜索