列舉類別與密封類別#

列舉類別(Enum Class) 用於定義一組固定的常數;密封類別(Sealed Class) 則用於定義一組有限的子類別,兩者都常與 when 搭配使用。


1. 列舉類別(Enum Class)#

基本宣告#

1
2
3
4
5
6
7
8
enum class Direction {
    NORTH, SOUTH, EAST, WEST
}

fun main() {
    val dir = Direction.NORTH
    println(dir) // 輸出:NORTH
}

搭配 when 使用#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
enum class Direction {
    NORTH, SOUTH, EAST, WEST
}

fun describe(dir: Direction): String {
    return when (dir) {
        Direction.NORTH -> "往北"
        Direction.SOUTH -> "往南"
        Direction.EAST  -> "往東"
        Direction.WEST  -> "往西"
    }
}

fun main() {
    println(describe(Direction.EAST)) // 輸出:往東
}

使用 when 搭配列舉時,編譯器會確認所有分支都已處理,不需要 else


列舉帶屬性#

每個列舉常數可以攜帶屬性值。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
enum class Planet(val mass: Double, val radius: Double) {
    MERCURY(3.303e+23, 2.4397e6),
    VENUS(4.869e+24, 6.0518e6),
    EARTH(5.976e+24, 6.37814e6)
}

fun main() {
    val earth = Planet.EARTH
    println("地球質量:${earth.mass}") // 輸出:地球質量:5.976E24
}

列舉的內建屬性與方法#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
enum class Color { RED, GREEN, BLUE }

fun main() {
    val c = Color.GREEN
    println(c.name)    // 輸出:GREEN(名稱字串)
    println(c.ordinal) // 輸出:1(索引,從 0 開始)

    // 從字串取得列舉值
    val parsed = Color.valueOf("BLUE")
    println(parsed)    // 輸出:BLUE

    // 取得所有列舉值
    Color.entries.forEach { println(it) }
    // 輸出:RED GREEN BLUE
}

2. 密封類別(Sealed Class)#

特性#

  1. 密封類別的所有子類別必須定義在 同一個套件(package)中。
  2. 編譯器能掌握所有子類別,與 when 搭配時可提供窮舉檢查。
  3. 子類別可以是 classdata classobject,可攜帶不同的資料。

基本宣告#

1
2
3
4
5
sealed class Result {
    data class Success(val data: String) : Result()
    data class Error(val message: String) : Result()
    object Loading : Result()
}

搭配 when 使用#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
sealed class Result {
    data class Success(val data: String) : Result()
    data class Error(val message: String) : Result()
    object Loading : Result()
}

fun handle(result: Result) {
    when (result) {
        is Result.Success -> println("成功:${result.data}")
        is Result.Error   -> println("錯誤:${result.message}")
        Result.Loading    -> println("載入中…")
    }
}

fun main() {
    handle(Result.Success("使用者資料"))  // 輸出:成功:使用者資料
    handle(Result.Error("網路逾時"))      // 輸出:錯誤:網路逾時
    handle(Result.Loading)                // 輸出:載入中…
}

實際應用:網路請求狀態#

密封類別最常見的用途是表示有限的狀態集合,例如 API 請求的結果。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
sealed class ApiResponse {
    data class Success(val body: String) : ApiResponse()
    data class HttpError(val code: Int, val message: String) : ApiResponse()
    object NetworkFailure : ApiResponse()
}

fun processResponse(response: ApiResponse) {
    when (response) {
        is ApiResponse.Success ->
            println("回應內容:${response.body}")
        is ApiResponse.HttpError ->
            println("HTTP 錯誤 ${response.code}${response.message}")
        ApiResponse.NetworkFailure ->
            println("無法連線至伺服器")
    }
}

fun main() {
    processResponse(ApiResponse.Success("{ \"status\": \"ok\" }"))
    processResponse(ApiResponse.HttpError(404, "Not Found"))
    processResponse(ApiResponse.NetworkFailure)
    // 輸出:
    // 回應內容:{ "status": "ok" }
    // HTTP 錯誤 404:Not Found
    // 無法連線至伺服器
}

3. 列舉類別 vs. 密封類別#

比較項目列舉類別密封類別
每個常數的資料共用相同屬性結構各子類別可有不同屬性
實例數量每個常數只有一個實例子類別可建立多個實例
適用場景固定常數集合(方向、顏色、狀態碼)有限的資料型別集合(網路結果、UI 狀態)

Reference#