列舉

列舉型別屬於特殊類別,用來表示數量固定的常數集合

所有列舉都會自動繼承 Enum 類別。同時列舉屬於密封類型(sealed):無法被繼承、實作、混入,也不能手動明確建立執行個體

抽象類別與混入(mixin)可以明確實作或繼承 Enum,但若沒有被列舉宣告實作或混入,就無法建立該類別或混入的物件。

使用 enum 關鍵字,列出所有列舉常數即可定義簡易列舉:

enum Color { red, green, blue }

宣告列舉時,允許在最後加上逗號,方便複製貼上,減少程式錯誤。

強化列舉(Enhanced enums)

Dart 支援強化列舉,可以像一般類別一樣定義成員欄位、方法、常數建構子,但執行個體只能是預先定義好的固定常數。

宣告強化列舉有數項強制規範:

  1. 執行個體變數必須標記為 final(包含混入引入的變數)
  2. 所有產生式建構子都必須加上 const
  3. 工廠建構子只能回傳列舉內已定義的常數執行個體
  4. 不可再明確繼承其他類別(自動繼承 Enum
  5. 不能覆寫 indexhashCode、相等運算子 ==
  6. 列舉內不能定義名稱為 values 的成員,會與自動產生的靜態 values 存取子衝突
  7. 所有列舉執行個體必須寫在列舉定義最開頭,且至少要有一個執行個體
  8. 強化列舉的執行個體方法中,可以使用 this 代表目前的列舉常數

範例

enum Vehicle implements Comparable<Vehicle> {
  car(tires: 4, passengers: 5, carbonPerKilometer: 400),
  bus(tires: 6, passengers: 50, carbonPerKilometer: 800),
  bicycle(tires: 2, passengers: 1, carbonPerKilometer: 0);

  const Vehicle({
    required this.tires,
    required this.passengers,
    required this.carbonPerKilometer,
  });

  final int tires;
  final int passengers;
  final int carbonPerKilometer;

  // Getter
  int get carbonFootprint => (carbonPerKilometer / passengers).round();
  bool get isTwoWheeled => this == Vehicle.bicycle;

  @override
  int compareTo(Vehicle other) => carbonFootprint - other.carbonFootprint;
}Code language: Dart (dart)

Dart 3.13 以上版本支援主要建構子,簡化強化列舉的撰寫方式:

enum Vehicle(
  final int tires,
  final int passengers,
  final int carbonPerKilometer,
) implements Comparable<Vehicle> {
  car(4, 5, 400),
  bus(6, 50, 800),
  bicycle(2, 1, 0);

  int get carbonFootprint => (carbonPerKilometer / passengers).round();
  bool get isTwoWheeled => this == Vehicle.bicycle;

  @Override
  int compareTo(Vehicle other) => carbonFootprint - other.carbonFootprint;
}Code language: Dart (dart)

列舉使用方式

存取列舉常數的方式與靜態變數相同:

final favoriteColor = Color.blue;
if (favoriteColor == Color.blue) {
  print('Your favorite color is blue!');
}Code language: Dart (dart)

index 屬性

每個列舉常數都內建 index,回傳在列舉定義中從 0 開始的序號

assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);Code language: Dart (dart)

values 靜態常數

透過 列舉名稱.values 取得包含所有列舉值的清單:

List<Color> colors = Color.values;
assert(colors[2] == Color.blue);Code language: Dart (dart)

搭配 switch 使用列舉

switch 內使用列舉時,如果沒有覆蓋全部列舉分支,編譯器會發出警告;你可以使用 default 作為備案分支。

var aColor = Color.blue;

switch (aColor) {
  case Color.red:
    print('Red as roses!');
  case Color.green:
    print('Green as grass!');
  default:
    print(aColor); // Color.blue
}Code language: Dart (dart)

name 屬性

透過 .name 取得列舉常數對應的字串名稱:

print(Color.blue.name); // 輸出 "blue"Code language: Dart (dart)

存取列舉自訂成員

強化列舉可以像一般物件一樣存取自訂欄位、Getter、方法:

print(Vehicle.car.carbonFootprint);Code language: CSS (css)

接下來是本課完整範例,我們建立月份列舉類別,實作上面學到的所有觀念。

enum Month implements Comparable<Month> {
  january(1, '一月', days: 31),
  february(2, '二月', days: 28),
  march(3, '三月', days: 31),
  april(4, '四月', days: 30),
  may(5, '五月', days: 31),
  june(6, '六月', days: 30),
  july(7, '七月', days: 31),
  august(8, '八月', days: 31),
  september(9, '九月', days: 30),
  october(10, '十月', days: 31),
  november(11, '十一月', days: 30),
  december(12, '十二月', days: 31);

  const Month(this.monthNumber, this.cnName, {required this.days});

  /// 公曆月份數字 1~12
  final int monthNumber;

  /// 名稱
  final String cnName;

  /// 平年天數
  final int days;

  /// Getter:是否屬於第一季
  bool get isFirstQuarter => monthNumber <= 3;

  /// Getter:是否有31天
  bool get has31Days => days == 31;

  /// 自訂方法:判斷二月是否為閏年
  bool isLeapYearFebruary(int year) {
    if (this != Month.february) return false;
    return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
  }

  /// 實作 Comparable,支援排序
  @Override
  int compareTo(Month other) {
    return monthNumber - other.monthNumber;
  }
}

void main() {
  // 1. 基礎存取列舉常數
  Month current = Month.august;
  print('目前列舉:$current');
  print('列舉name屬性:${current.name}');
  print('月份:${current.cnName}');
  print('月份編號1~12:${current.monthNumber}');
  print('平年天數:${current.days}');
  print('是否31天月份:${current.has31Days}');
  print('是否第一季:${current.isFirstQuarter}');

  // 2. index 屬性(列舉定義順序索引,從0開始)
  print('\n${Month.january.name} index = ${Month.january.index}');
  print('${Month.december.name} index = ${Month.december.index}');

  // 3. 使用 values 取得全部列舉清單
  List<Month> allMonths = Month.values;
  print('\n總共有 ${allMonths.length} 個月份');

  // 4. switch 使用列舉
  print('\n==== switch 判斷 ====');
  switch (current) {
    case Month.january:
      print('一年之始');
    case Month.february:
      print('可能存在閏日');
    case Month.march:
    case Month.april:
    case Month.may:
      print('春季');
    case Month.june:
    case Month.july:
    case Month.august:
      print('夏季');
    case Month.september:
    case Month.october:
    case Month.november:
      print('秋季');
    case Month.december:
      print('冬季年末');
  }

  // 5. 呼叫自訂方法
  Month feb = Month.february;
  print('\n2024年是否閏二月:${feb.isLeapYearFebruary(2024)}');
  print('2025年是否閏二月:${feb.isLeapYearFebruary(2025)}');

  // 6. 列舉排序(依靠 Comparable 介面)
  var selected = [Month.october, Month.january, Month.july];
  selected.sort();
  print('\n排序後月份:${selected.map((m) => m.cnName).toList()}');
}Code language: Dart (dart)

執行結果

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
目前列舉:Month.august
列舉name屬性:august
月份:八月
月份編號1~128
平年天數:31
是否31天月份:true
是否第一季:false

january index = 0
december index = 11

總共有 12 個月份

==== switch 判斷 ====
夏季

2024年是否閏二月:true
2025年是否閏二月:false

排序後月份:[一月, 七月, 十月]Code language: JavaScript (javascript)

列舉

Previous:

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *