列挙型

整数定数による状態管理の限界

プログラムでは、曜日や信号機のように、限られた選択肢の中から1つの状態を選んで管理したい場面がよくあります。 このような状態を管理する素朴な方法として、整数型の定数を使用する方法があります。 プログラムは以下のようになります。

TrafficLight.java

class TrafficLight {
  public static final int RED = 0;
  public static final int YELLOW = 1;
  public static final int GREEN = 2;
}

このクラスを用いて、現在の信号の状態に応じたメッセージを表示するプログラムは以下のようになります。

void printMessage(int light) {
  if (light == TrafficLight.RED) {
    println("止まれ");
  } else if (light == TrafficLight.YELLOW) {
    println("注意");
  } else if (light == TrafficLight.GREEN) {
    println("進め");
  } else {
    println("不明な信号です");
  }
}

void setup() {
  // 正しい定数を渡す
  printMessage(TrafficLight.RED);

  // 定義されていない無効な数値を渡す
  printMessage(999);
}

実行結果

止まれ
不明な信号です

この方法には、安全性の観点から課題があります。 printMessage メソッドの引数は int 型であるため、定数として定義されている 01 以外の数値も渡すことができてしまいます。 コンパイラはこれが無効な値であることを検知できないため、プログラムを実行するまでバグに気付けません。 制限された範囲の値だけを安全に扱いたい場面で、通常の整数型を使用するのは危険です。

列挙型による解決

この課題を解決するために導入されたのが 列挙型 です。 列挙型は、あらかじめ定義した限定された値だけを代入できる特別なデータ型です。 Javaでは enum キーワードを用いて定義します。 プログラムは以下のようになります。

TrafficLight.java

enum TrafficLight {
  RED,
  YELLOW,
  GREEN
}

この列挙型を利用するプログラムは以下のようになります。

void printMessage(TrafficLight light) {
  if (light == TrafficLight.RED) {
    println("止まれ");
  } else if (light == TrafficLight.YELLOW) {
    println("注意");
  } else if (light == TrafficLight.GREEN) {
    println("進め");
  }
}

void setup() {
  // 列挙型の要素を渡す
  printMessage(TrafficLight.RED);

  // 定義されていない値を渡そうとするとコンパイルエラーになる
  // printMessage(999); // エラー:型が一致しないためコンパイルできない
}

実行結果

止まれ

メソッドの引数の型を TrafficLight に制限したことで、TrafficLight に定義されている要素以外の値を渡すことが不可能になります。 無効な値を渡そうとするとコンパイルエラーになるため、バグを未然に防ぐことができます。 これが、列挙型による型安全性の確保です。

クラスとしての列挙型

Javaの列挙型は、単なる定数の羅列ではありません。 実体としては特別なクラスであり、フィールド、コンストラクタ、メソッドを定義できます。

例えば、それぞれの信号の状態に「表示するメッセージ」のデータを紐付け、それを取り出すメソッドを追加するプログラムは以下のようになります。

TrafficLight.java

enum TrafficLight {
  // コンストラクタを呼び出して要素を定義する
  RED("止まれ"),
  YELLOW("注意"),
  GREEN("進め");

  private String message;

  // コンストラクタ
  private TrafficLight(String message) {
    this.message = message;
  }

  // メソッド
  public String getMessage() {
    return this.message;
  }
}

この列挙型を利用するプログラムは以下のようになります。

void setup() {
  TrafficLight light = TrafficLight.RED;

  // メソッドを呼び出して紐付けられたメッセージを取得する
  println("REDのメッセージ: " + light.getMessage());

  // values()メソッドを用いて、すべての要素を繰り返し処理する
  for (TrafficLight t : TrafficLight.values()) {
    println(t.name() + "のメッセージ: " + t.getMessage());
  }
}

実行結果

REDのメッセージ: 止まれ
REDのメッセージ: 止まれ
YELLOWのメッセージ: 注意
GREENのメッセージ: 進め

列挙型のコンストラクタは、外部から new で呼び出すことはできません。 列挙型の中で定義されている要素が読み込まれる際に、自動的に呼び出されます。 コンストラクタのアクセス修飾子は private に指定するルールがあります。

列挙型に定義されたすべての要素を配列として取得したい場合は、values メソッドを使用します。 また、name メソッドを呼び出すことで、要素の名前を文字列として取得できます。 データとそれを処理するメソッドを一体化させることで、よりすっきりとした安全なプログラムを記述できます。

演習

演習1

状態の管理に整数定数を使用する場合と比較して、列挙型を使用するメリットを安全性の観点から説明しなさい。

演習2

曜日を表す列挙型 Day を作成しなさい。 この列挙型は、月曜日から日曜日までの要素を持ち、それぞれの曜日が「休日」であるかどうかを判定する isWeekend() メソッドを持つものとする。 土曜日と日曜日のみが休日であると定義しなさい。 以下の setup 関数が正しく動作するように列挙型を設計しなさい。

void setup() {
  Day today = Day.SATURDAY;
  Day workDay = Day.MONDAY;

  println("土曜日は休日: " + today.isWeekend());
  println("月曜日は休日: " + workDay.isWeekend());
}

実行結果

土曜日は休日: true
月曜日は休日: false

演習3

商品カテゴリを表す列挙型 Category を作成しなさい。 この列挙型は、以下の3つの要素を持ち、それぞれのカテゴリに応じた税率を保持するものとする。

  • FOOD: 税率 0.08
  • CLOTHES: 税率 0.10
  • BOOK: 税率 0.10

また、商品の税抜金額とカテゴリを元に、税込価格を計算する静的メソッド calculatePrice を定義しなさい。 以下の setup 関数が正しく動作するように列挙型を設計しなさい。

// ここに静的メソッドcalculatePriceを定義する
int calculatePrice(int price, Category category) {
  double tax = price * category.getTaxRate();
  return price + (int) tax;
}

void setup() {
  int foodPrice = calculatePrice(1000, Category.FOOD);
  int bookPrice = calculatePrice(1500, Category.BOOK);

  println("食料品の税込価格: " + foodPrice);
  println("本の税込価格: " + bookPrice);
}

実行結果

食料品の税込価格: 1080
本の税込価格: 1650

演習の解答例

演習1の解答例

整数定数を使用する場合、引数として範囲外の無効な数値が渡されたとしても、コンパイル時点でそのエラーを検出することができない。 一方で列挙型を使用する場合、あらかじめ定義された要素以外の値を代入しようとするとコンパイルエラーになるため、無効な状態がプログラムに混入するのを防ぐことができる。

演習2の解答例

曜日を定義し、休日か否かの判定メソッドを実装した列挙型 Day の実装例は以下の通りである。 各要素の定義時に休日フラグをコンパイル時に渡し、isWeekend メソッドを通じてその状態を外部へ公開している。

Day.java

enum Day {
  MONDAY(false),
  TUESDAY(false),
  WEDNESDAY(false),
  THURSDAY(false),
  FRIDAY(false),
  SATURDAY(true),
  SUNDAY(true);

  private boolean weekend;

  private Day(boolean weekend) {
    this.weekend = weekend;
  }

  public boolean isWeekend() {
    return this.weekend;
  }
}

演習3の解答例

商品カテゴリと税率を管理する列挙型 Category の実装例は以下の通りである。 それぞれのカテゴリに対応する税率をフィールドとして保持し、getTaxRate メソッドで取得できるように設計している。

Category.java

enum Category {
  FOOD(0.08),
  CLOTHES(0.10),
  BOOK(0.10);

  private double taxRate;

  private Category(double taxRate) {
    this.taxRate = taxRate;
  }

  public double getTaxRate() {
    return this.taxRate;
  }
}

results matching ""

    No results matching ""