列挙型

列挙型は特殊なクラスで、要素数が固定された定数の集合を表現するために使われます。

すべての列挙型は自動的に Enum クラスを継承します。また列挙型はシールド型(sealed)であり、継承・実装・ミックスインができず、明示的にインスタンスを生成することも許可されません

抽象クラスとミックスインは明示的に Enum を実装または継承可能ですが、列挙型に実装・ミックスインされない限り、これらのクラスやミックスインの実体インスタンスを作成できません。

enum キーワードを使用し、列挙定数を列記することで単純な列挙型を定義できます。

enum Color { red, green, blue }

列挙定義の末尾にカンマを記述可能で、コピー&ペースト時のコードミスを抑えられます。

拡張列挙型(Enhanced enums)

Dartは拡張列挙型に対応しており、通常のクラス同様にメンバーフィールド、メソッド、定数コンストラクタを定義できます。ただしインスタンスは事前に宣言した固定の定数だけに限定されます。

拡張列挙型を定義する際には、複数の制約が強制されます。

  1. インスタンス変数はすべて final とする必要があります(ミックスインから導入された変数も含む)
  2. すべての生成コンストラクタに const を付記しなければならない
  3. ファクトリコンストラクタは、列挙内で定義済みの定数インスタンスのみを返せる
  4. 他のクラスを明示的に継承できない(Enum が自動継承される)
  5. indexhashCode、等号演算子 ==オーバーライドすることは禁止
  6. 列挙内部に values という名前のメンバーを定義できない。自動生成される静的 values アクセサと衝突するため
  7. すべての列挙インスタンスは定義の先頭に記述し、少なくとも1つのインスタンスを記載する必要がある
  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:第1四半期か判定
  bool get isFirstQuarter => monthNumber <= 3;

  /// Getter:31日ある月か判定
  bool get has31Days => days == 31;

  /// 独自メソッド:2月が閏年か確認
  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('第1四半期か:${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
第1四半期か:false

january index = 0
december index = 11

合計 12 か月

==== switch判定 ====
夏

2024年は閏二月:true
2025年は閏二月:false

ソート後の月:[一月, 七月, 十月]Code language: JavaScript (javascript)

列挙型

Previous:

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です