列舉型別屬於特殊類別,用來表示數量固定的常數集合。
所有列舉都會自動繼承 Enum 類別。同時列舉屬於密封類型(sealed):無法被繼承、實作、混入,也不能手動明確建立執行個體。
抽象類別與混入(mixin)可以明確實作或繼承 Enum,但若沒有被列舉宣告實作或混入,就無法建立該類別或混入的物件。
使用 enum 關鍵字,列出所有列舉常數即可定義簡易列舉:
enum Color { red, green, blue }
宣告列舉時,允許在最後加上逗號,方便複製貼上,減少程式錯誤。
強化列舉(Enhanced enums)
Dart 支援強化列舉,可以像一般類別一樣定義成員欄位、方法、常數建構子,但執行個體只能是預先定義好的固定常數。
宣告強化列舉有數項強制規範:
- 執行個體變數必須標記為
final(包含混入引入的變數) - 所有產生式建構子都必須加上
const - 工廠建構子只能回傳列舉內已定義的常數執行個體
- 不可再明確繼承其他類別(自動繼承
Enum) - 不能覆寫
index、hashCode、相等運算子== - 列舉內不能定義名稱為
values的成員,會與自動產生的靜態values存取子衝突 - 所有列舉執行個體必須寫在列舉定義最開頭,且至少要有一個執行個體
- 強化列舉的執行個體方法中,可以使用
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~12:8
平年天數:31
是否31天月份:true
是否第一季:false
january index = 0
december index = 11
總共有 12 個月份
==== switch 判斷 ====
夏季
2024年是否閏二月:true
2025年是否閏二月:false
排序後月份:[一月, 七月, 十月]Code language: JavaScript (javascript)
列舉
Previous: Mixins