Kotlin Sealed Classes (密封類別)

Sealed Class (密封類別) 是一種特殊的類別,用來表示 受限的繼承結構 (Restricted Class Hierarchies)。簡而言之,它就像是超級強大的 Enum

當一個類別被標記為 sealed 時,它所有的子類別都必須定義在同一個檔案中(Kotlin 1.5 之後放寬到同一個編譯模組/套件中)。這讓編譯器能確切知道:除了這幾個子類別,再也沒有其他的了。

為什麼需要 Sealed Class?

雖然 Enum 可以定義常數,但每個 Enum 常數只能有同一組參數。而 Sealed Class 的每個子類別可以擁有各自不同的屬性和狀態。

定義 Sealed Class

最常見的應用場景是用來定義 UI 狀態 (UI State)

sealed class UiState {
    // 單例狀態:載入中 (不需要帶資料)
    object Loading : UiState()
    
    // 成功狀態:攜帶資料 List<String>
    data class Success(val data: List<String>) : UiState()
    
    // 錯誤狀態:攜帶錯誤訊息 Exception
    data class Error(val exception: Exception) : UiState()
}

搭配 when 使用

因為 Sealed Class 的子類別是有限的 (Known at compile time),所以編譯器知道所有的可能性。

Sealed Class 最強大的地方在於搭配 when 表達式。因為編譯器知道所有的子類別,所以如果你漏掉了某種情況,它會直接報錯,你甚至不需要寫 else 分支。

fun handleState(state: UiState) {
    when (state) {
        is UiState.Loading -> {
            println("Loading...")
        }
        is UiState.Success -> {
            println("Got data: ${state.data}")
        }
        is UiState.Error -> {
            println("Error: ${state.exception.message}")
        }
        // 不需要 else!
    }
}

Sealed Interface

從 Kotlin 1.5 開始,你也使用 sealed interface。如果你不需要繼承任何實作邏輯,只想要定義型別階層,sealed interface 會比 sealed class 更輕量且更有彈性 (因為類別只能單一繼承,但介面可以多重實作)。

sealed interface Result
data class Success(val value: Int) : Result
data class Failure(val error: String) : Result

總結:Enum vs Sealed Class

  • Enum: 狀態是固定的常數,不能帶動態資料 (例如 Color.RED, Direction.NORTH)。
  • Sealed Class: 狀態是有限的類別集合,每個狀態可以攜帶不同的資料實例 (例如 Success(data), Error(msg))。

什麼時候該用 Sealed Classes?

只要你的資料結構有「這類東西只有這幾種可能」的特性,且每一種可能需要的資訊不同時,Sealed Class 就是最佳選擇。