Java : 列挙型(enum)の基本
列挙型は、複数の異なる定数を1つの集まりとして宣言できます。
例えば DayOfWeek(曜日) という列挙型を宣言して、その列挙型に MONDAY(月曜日)、TUESDAY(火曜日) ... SUNDAY(日曜日) と定数を定義します。
本記事では、そんな列挙型の基本的な使い方をご紹介します。
概要
列挙型(クラス) は、Java言語仕様に組み込まれている機能です。
複数の異なる定数を1つの集まり(集合)として宣言することができます。
列挙型で定義された定数のことを、本記事では公式の翻訳にならって 列挙型定数 と表記します。
他のプログラミング言語では、識別子、列挙子ともいわれています。
簡単な例
さっそくコード例を見ていきましょう。
月曜日、火曜日、水曜日…という曜日を、列挙型で宣言してみます。
enum DayOfWeek {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY,
}
enum キーワードを使い、その後に列挙型の名前がきます。
今回、列挙型の名前は DayOfWeek (曜日)としました。
そして、その曜日という集まりに、
- MONDAY (月曜日)
- TUESDAY (火曜日)
- ...
- SUNDAY (日曜日)
という列挙型定数が定義されていきます。
それでは、実際にこの列挙型を使ってみましょう。
void main() {
func(DayOfWeek.SUNDAY); // 今日は...週末!
func(DayOfWeek.WEDNESDAY); // 今日は...平日!
}
void func(DayOfWeek dayOfWeek) {
System.out.print("今日は...");
if (dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY) {
System.out.println("週末!");
} else {
System.out.println("平日!");
}
}
列挙型定数を使うには、
- 列挙型名 + . (ピリオド) + 列挙型定数名
とします。new は必要ありません。
補足
- 標準APIには、今回例とした列挙型と同名のDayOfWeek があります。
ただし、便利なメソッドが追加されていたりと、少し拡張されたものとなっています。
列挙型の仕様
列挙型定数の型
enum DayOfWeek {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY,
}
列挙型定数の型は、列挙型そのものとなります。
final DayOfWeek monday = DayOfWeek.MONDAY;
System.out.println(monday); // MONDAY
DayOfWeek.MONDAY (列挙型定数) の型は DayOfWeek になるわけですね。
列挙型の変数には、列挙型定数と null だけが代入できます。
数値や文字列は代入できません。
final DayOfWeek aaa = null; // OK
final DayOfWeek bbb = 0; // コンパイルエラー
final DayOfWeek ccc = "MONDAY"; // コンパイルエラー
本質的にはクラス
enum で宣言された列挙型は、上記のEnumクラスを暗黙的に継承します。
つまり、列挙型は本質的にはクラスでありObjectでもあります。
例えば、
enum Sample {
AAA, BBB, CCC,
}
と宣言された列挙型は、暗黙的に次のように宣言されます。
enum Sample extends Enum<Sample> {
AAA, BBB, CCC,
}
よって、列挙型定数は Enumクラスの各種メソッドが使えます。
// ordinal メソッドは、列挙型定数が定義された順番(序数)を取得します。
// 序数は 0 から採番されます。
System.out.println(Sample.AAA.ordinal()); // 0
System.out.println(Sample.BBB.ordinal()); // 1
System.out.println(Sample.CCC.ordinal()); // 2
// toString メソッドは、列挙型定数名を文字列として取得します。
System.out.println(Sample.AAA.toString()); // AAA
System.out.println(Sample.BBB.toString()); // BBB
System.out.println(Sample.CCC.toString()); // CCC
それ以外の API 使用例は、下記の記事もご参照ください。
さらに、列挙型には、Enumクラスでは宣言されていない2つのメソッドが暗黙的に宣言されます。
- public static T valueOf (String name) : 指定した名前の列挙型定数を取得する
- public static T[] values () : すべての列挙型定数を取得する
イメージとしては次のような宣言となります。
enum Sample extends Enum<Sample> {
AAA, BBB, CCC;
public static Sample valueOf(String name) {
...
}
public static Sample[] values() {
...
}
}
暗黙的に宣言されたメソッドを使う例になります。
final Sample aaa = Sample.valueOf("AAA");
System.out.println(aaa); // AAA
final Sample[] values = Sample.values();
System.out.println(Arrays.toString(values)); // [AAA, BBB, CCC]
注意点として、暗黙的に宣言される内容を、明示的に宣言しようとするとコンパイルエラーになります。
あくまで暗黙的に宣言されるのが前提となります。
// ソースコード上で次のように書くと、コンパイルエラーとなります。
enum Sample extends Enum<Sample> {
AAA, BBB, CCC,
}
列挙型定数のインスタンスは常に1つ
列挙型定数のインスタンスは常に1つであることが、Java言語仕様で保証されています。
そのため、列挙型定数の比較には equals メソッドではなく、== 演算子が使えます。
System.out.println(Sample.AAA == Sample.AAA); // true
System.out.println(Sample.AAA == Sample.BBB); // false
System.out.println(Sample.AAA == Sample.CCC); // false
インスタンスが1つであることが保証できなくなる操作はできません。
// 複製や、新しいインスタンスを生成することはできません。
final Sample clonedAAA = Sample.AAA.clone(); // コンパイルエラー
final Sample newAAA = new Sample("AAA", 0); // コンパイルエラー
switch式では列挙型定数名だけでアクセス可能
通常、列挙型定数にアクセスするには、
- 列挙型名 + . (ピリオド) + 列挙型定数名
とします。(if文の判定で使う場合など)
enum Sample {
AAA, BBB, CCC,
}
void func1(Sample sample) {
if (sample == Sample.AAA || sample == Sample.BBB) {
System.out.println("AAA or BBB");
} else {
System.out.println("それ以外!");
}
}
switch式 (もしくはswitch文) では、列挙型名は省略してアクセスできます。
void func2(Sample sample) {
switch (sample) {
case AAA, BBB -> System.out.println("AAA or BBB");
default -> System.out.println("それ以外!");
}
}
列挙型の拡張
enum は本質的にクラスであるため、クラスと同様の拡張ができます。
それでは、具体的なコード例で見ていきましょう。
enum Bill {
YEN_1000,
YEN_5000,
YEN_10000,
}
例として、Bill (紙幣)という列挙型を宣言しました。
列挙型定数として
- YEN_1000 (千円札)
- YEN_5000 (五千円札)
- YEN_10000 (一万円札)
を定義しています。
単純に紙幣の種類だけを区別したいなら、これだけでいいかもしれません。
しかし、例えば紙幣の円の合計を計算したい、というときに少しだけ不便かもしれません。
まずは列挙型を拡張しない例を見てみましょう。
// listにある紙幣の円の合計を算出したいとします。
final var list = new ArrayList<Bill>();
list.add(Bill.YEN_1000);
list.add(Bill.YEN_1000);
list.add(Bill.YEN_5000);
list.add(Bill.YEN_5000);
list.add(Bill.YEN_5000);
list.add(Bill.YEN_10000);
int total = 0;
for (final var bill : list) {
switch (bill) {
case YEN_1000 -> total += 1000;
case YEN_5000 -> total += 5000;
case YEN_10000 -> total += 10000;
}
}
// 合計 : 27000 円
System.out.println("合計 : " + total + " 円");
for文で条件分岐の処理が必要となりました。
コードとしては、できる限り条件分岐は減らしたいですね。
フィールドとメソッドを追加
それでは、Bill (紙幣) 列挙型を少し拡張してみましょう。
それぞれの列挙型定数に、実際の円の値を整数で持つようにします。
enum Bill {
YEN_1000(1000),
YEN_5000(5000),
YEN_10000(10000),
;
private final int yen;
Bill(int yen) {
this.yen = yen;
}
int getYen() {
return yen;
}
}
円の値を保持するために、フィールドに
private final int yen;
を用意します。
そして、円の初期値を受け取るコンストラクタを用意します。
Bill(int yen) {
this.yen = yen;
}
列挙型定数の初期化時に、上記のコンストラクタを呼び出すようにします。
YEN_1000(1000),
YEN_5000(5000),
YEN_10000(10000),
そして、円の値を取得するメソッドを用意します。
int getYen() {
return yen;
}
最後に、列挙型定数の定義部分と、フィールドやコンストラクタといった拡張部分を
; (セミコロン)
で区切ります。
YEN_1000(1000),
YEN_5000(5000),
YEN_10000(10000),
;
これは
YEN_1000(1000),
YEN_5000(5000),
YEN_10000(10000);
としても、かまいません。
それでは、拡張した列挙型を使って、紙幣リストの円の合計を計算してみます。
final var list = new ArrayList<Bill>();
list.add(Bill.YEN_1000);
list.add(Bill.YEN_1000);
list.add(Bill.YEN_5000);
list.add(Bill.YEN_5000);
list.add(Bill.YEN_5000);
list.add(Bill.YEN_10000);
int total = 0;
for (final var bill : list) {
total += bill.getYen();
}
// 合計 : 27000 円
System.out.println("合計 : " + total + " 円");
switchによる条件分岐がなくなり、for文の処理がすっきりしましたね。
Stream APIを使うと、さらにスマートに記述できます。
final var total = list.stream().mapToInt(Bill::getYen).sum();
// 合計 : 27000 円
System.out.println("合計 : " + total + " 円");
抽象メソッドを追加
列挙型に抽象メソッドを宣言して、それぞれの列挙型定数で実装するアプローチも見てみましょう。
計算の結果は同じとなります。
enum Bill {
YEN_1000 {
@Override
int getYen() {
return 1000;
}
},
YEN_5000 {
@Override
int getYen() {
return 5000;
}
},
YEN_10000 {
@Override
int getYen() {
return 10000;
}
},
;
abstract int getYen();
}
シールクラス
列挙型定数は、インスタンスは常に1つという制限があります。
もし、列挙型のような網羅性を維持しつつ、インスタンスを複数生成したい…そんなときはシールクラスが使えるかもしれません。
シールクラスについては下記の記事にまとめていますので、よろしければご参照ください。
まとめ
列挙型、いかがでしたでしょうか。
単純な定数の定義だけではなく、ある程度の拡張も可能です。
有効に活用していきましょう。
関連記事
- API 使用例