新人エンジニア研修で知っておきたい列挙型の使い方
※この章は研修時間の都合により丸ごとスキップすることがあります。
なぜ、列挙型の理解が重要なのか、その理由。
この記事では、弊社の新人エンジニア研修の参考にJavaを解説します。
前回はコレクションとジェネリクスについて解説しました。
今回は日付/時刻と列挙型【enum】について解説します。
実は、日付と時刻はコンピューターにとって苦手な分野です。なぜなら、日付は各月で月末の日付が違いますし、閏年のせいで年間の日数も変わります。また、時刻は60秒で1分、60分で一時間、24時間で一日というように、単位の繰り上がりが一定しません。
そこで、偉大なる先人は、日付と時刻を、ある基準日時からの経過時間(ミリ秒)で表現することを思いつきました。皆さんがこれから触れる日付と時刻にまつわるクラスは、内部に経過ミリ秒を持っており、そこから日付や時刻を計算して扱うように設計されているのです。この点はJavaだけでなくほぼすべてのプログラミング言語で共通の仕様です。
Javaには過去の経緯から日付/時刻に関するクラスはたくさんあります。しかし、当社の新人エンジニア研修ではjava.timeパッケージの中のクラスの特定の機能に限定してお伝えします。
そして、列挙型はデータの種類をすべて列挙するのに使います。例えば、月は1月から12月まで12種類しかありません。このとき月を表現するのにint型を使ったとしたらどうでしょうか?まちがって256月というデータも作られてしまうかもしれませんね。そのようなことが無いようにすべてのデータを列挙するのに使用するのが列挙型です。
1.現在日時の取得
今現在を表すインスタンスはLocalDateTimeクラス(標準API)のnowメソッドで取得できます。
package chap15;
import java.time.LocalDateTime;
public class Example01 {
public static void main(String[] args) {
LocalDateTime date = LocalDateTime.now();
System.out.println(date);
}
}
<実行結果>
2022-06-23T13:37:43.995 |
結果は「西暦-月-日T時:分:秒」の形で表示されました。ちなみに上記の“T”は単なる区切り文字で意味はありません。
インスタンスから年だけ、月だけ、日だけ、、、のように一部の要素を取り出すには以下のようにgetで始まるメソッドを使用します。
package chap15;
import java.time.LocalDateTime;
public class Example02 {
public static void main(String[] args) {
LocalDateTime date = LocalDateTime.now();
System.out.println(date.getYear() + "年");
System.out.println(date.getMonth() + "月");
System.out.println(date.getDayOfMonth() + "日");
System.out.println(date.getDayOfWeek() + "曜日");
System.out.println(date.getHour() + "時");
System.out.println(date.getMinute() + "分");
System.out.println(date.getSecond() + "秒");
System.out.println(date.getNano() + "ナノ秒");
}
}
<実行結果の例>
2022年 JUNE月 23日 THURSDAY曜日 13時 20分 58秒 718000000ナノ秒 |
ところで、ここで出てきた「FEBRUARY」、「WEDNESDAY」といった文字列は何でしょうか?
実はこれらは列挙型と呼ばれるJavaの型の一つです。列挙型は複数の定数をひとつにまとめておくことができる型です。月はJANUARYから始まって12個しかありません。曜日はMONDAYから始まって7個しかありません。このように限定的なものは列挙型にすると色々と便利なのです。
列挙型については本章でこの後学んでいきます。
2.特定の日付の扱い
特定の日付のインスタンスを作成するにはLocalDateクラスのstaticメソッドofを使います。また、文字列から変換するにはparseメソッドを使います。
以下は1995年4月19日生まれの人の誕生日を表示して、その後1969年11月9日生まれの人の誕生日を表示するソースコードです。
package chap15;
import java.time.LocalDate;
public class Example03 {
public static void main(String[] args) {
LocalDate birthday = LocalDate.of(1995, 4, 19);
System.out.println(birthday);
birthday = LocalDate.parse("1969-11-09");
System.out.println(birthday);
}
}
<実行結果>
1995-04-19 1969-11-09 |
なお、ありえない日付を指定すると例外が発生します。以下のコードでは、4月31日というありえない日付を設定しため例外が発生します。
package chap15;
import java.time.LocalDate;
public class Example04 {
public static void main(String[] args) {
LocalDate birthday = null;
try {
birthday = LocalDate.of(1995, 4, 31);
} catch (Exception e) {
System.out.println(e);
}
System.out.println(birthday);
}
}
<実行結果>
java.time.DateTimeException: Invalid date 'APRIL 31' null |
3.日付の書式を指定する
日付の書式を指定するには、クラスDateTimeFormatter(標準API)を使います。
先にサンプルコードを見てみます。
package chap15;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class Example05 {
public static void main(String[] args) {
LocalDate birthday = LocalDate.of(1995, 4, 19);
System.out.println(DateTimeFormatter.ofPattern("uuuu/M/d(E)", Locale.JAPANESE).format(birthday));
}
}
クラスDateTimeFormatterのofPatternメソッドは引数として書式とロケールという同じ言語を共有する区域を指定します。上記の例で書式は、uuuuが西暦を、Mが月を、dが日付を、Eが曜日をそれぞれ意味しています。例えば、月を2桁で表示したければMM、日付を2桁で表示したければddとします。試してみてください。ロケールはLocale.JAPANESEとなっています。
DateTimeFormatterのofPatternメソッドの戻り値に対してメソッドチェーンでformatメソッドを呼んで、引数にクラスLocalDateのインスタンスを渡せば、上記のような結果になります。
なお、和暦の表示も可能です。以下のサンプルコードを御覧ください。
package chap15;
import java.time.chrono.JapaneseDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class Example06 {
public static void main(String[] args) {
JapaneseDate japaneseDate = JapaneseDate.now();
System.out.println(DateTimeFormatter.ofPattern("G y年M月d日(E)", Locale.JAPANESE).format(japaneseDate));
}
}
<実行結果>
令和 4年6月23日(木) |
4.時刻の扱い
時刻を表すクラスは、クラスLocalTime(標準API)です。
クラスLocalTimeのofメソッドの引数に時間(0~23)と分(0~59)をカンマで区切って指定します。
以下のサンプルコードを御覧ください。
package chap15;
import java.time.LocalTime;
public class Example07 {
public static void main(String[] args) {
LocalTime localTime1 = LocalTime.of(0, 0);
LocalTime localTime2 = LocalTime.of(23, 59);
System.out.println(localTime1);
System.out.println(localTime2);
}
}
<実行結果>
00:00 23:59 |
なお、上記の例では分までしか指定していませんが、秒、ナノ秒まで指定が可能です。第3、第4引数を使います。
時刻の比較も可能です。クラスLocalTimeのインスタンスメソッドisBeforeやisAfterを使います。
次のサンプルコードをご覧ください。
package chap15;
import java.time.LocalTime;
public class Example08 {
public static void main(String[] args) {
LocalTime localTime1 = LocalTime.of(10, 0);
LocalTime localTime2 = LocalTime.of(15, 0);
System.out.println(localTime1.isBefore(localTime2));
System.out.println(localTime1.isAfter(localTime2));
}
}
<実行結果>
true false |
このようにたくさんのクラス、メソッドがあります。とても全ては紹介しきれないので、この新人エンジニア研修の期間では紹介しきれません。
皆さんは必要に応じて調べて使ってください。
5.Enum(列挙型)とは
Enum(イーナム)型は複数の定数をひとつにまとめることが出来る型のことです。Enumを使用することでプログラムが見易くなります。
ここではクラスEnum(標準API)について簡単な説明をします。
Enumは、クラスのように定義されますが、キーワード「enum」で始まります。次に、定数値のリストをカンマで区切って列挙します。定数は、大文字で表記されます。
例えば、以下のように定義されたEnumは12か月を表します。
enum Month {
JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE,
JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER
}
また、以下のように定義されたEnumは、1週間の曜日を表します。
enum Weekday { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }
このEnumを使用すると、次のように定数を参照できます。
Weekday today = Weekday.MONDAY;
他にEnumを使用すると便利なものを身近に探してみましょう。
あなたの答え: |
Enumは、一般的に、定数値のリストによって定義される単純なタイプに使用されますが、Enumはフィールド、メソッド、コンストラクタを持つことができ、クラスと同様に使用できます。
例えば、次のように定義されたEnumは、カラーを表し、RGB値を持ちます。
enum Color { RED(255, 0, 0), GREEN(0, 255, 0), BLUE(0, 0, 255); private int red; private int green; private int blue; private Color(int red, int green, int blue) { this.red = red; this.green = green; this.blue = blue; } public int getRed() { return red; } public int getGreen() { return green; } public int getBlue() { return blue; } }
このEnumを使用すると、次のようにRGB値を取得できます。
Color color = Color.RED;
int red = color.getRed(); // 255
int green = color.getGreen(); // 0
int blue = color.getBlue(); // 0
Enumは限定されたものを列挙するのに使うと便利でした。
Enum型の宣言には、クラスのかわりにenumキーワード(先頭小文字)を使います。
以下CupSize.javaというサンプルコードを見てください。
イメージとしては、コーヒーショップでカップのサイズが4つある(4つしかない)ということを表現したいとします。
package chap15;
public enum CupSize {
SHORT,
TALL,
GRANDE,
VENTI;
}
定数なのですべて大文字で表記します。また、要素名はカンマで区切って記述します。
これがもしも以前学んだ定数で扱うとすると以下のようになります。
public static final String SHORT = "Short";
public static final String TALL = "Tall";
public static final String GRANDE = "Grande";
public static final String VENTI = "Venti";
定数では4つのカップサイズはバラバラの定数でした。しかし、列挙型ではCupSizeという一つの方の中で関連する4つの定数を管理できるようになります。また、定数ではカップのサイズが4つしか無いということを表現できませんでしたが、列挙型では出来ています。
通常のクラスはインスタンスを作成する際にnew演算子を使いましたが、Enumでは使いません。
以下はサンプルです。
package chap15;
public class Example09 {
public static void main(String[] args) {
CupSize cup = CupSize.GRANDE;
System.out.println(cup);
}
}
<実行結果>
GRANDE |
Enumはデフォルトでメソッドをいくつか持っています。以下で説明しているvaluesメソッドは、Enumであれば宣言・実装をしなくても使えます。拡張for文と組み合わせて次のようなコードを書くことができます。
package chap15;
public class Example10 {
public static void main(String[] args) {
for (CupSize cup : CupSize.values()) {
System.out.println(cup);
}
}
}
<実行結果>
SHORT TALL GRANDE VENTI |
このようにvaluesメソッドを使えば全要素が配列として取得できるので、拡張For文の条件指定に使えます。
また、switch文の中でケースラベルに指定されることがよくあります。
Coffee クラスでCupSizeを使用してみます。コーヒーカップのサイズごとに違った料金を返すようにtoStringメソッドをオーバーライドしています。
package chap15;
public class Coffee {
private CupSize cupSize;
public Coffee(CupSize cupSize) {
this.cupSize = cupSize;
}
public String getPrice() {
switch (this.cupSize) {
case SHORT:
return "280円です";
case TALL:
return "320円です";
case GRANDE:
return "360円です";
case VENTI:
return "400円です";
default:
return "もう一度ご注文をお願いします。";
}
}
}
※今回のswitch文ではreturnしていますのでbreak文はありません。
テストクラスを書いてみます。
package chap15;
public class Example11 {
public static void main(String[] args) {
Coffee coffee = new Coffee(CupSize.GRANDE);
// Coffee coffee2 = new Coffee(CupSize.Large);// 列挙された以外のサイズは存在し得ないことになる
// Coffee coffee3 = new Coffee(CupSize.GRANDEE);// またスペルミスにも早期に気づくことができる
System.out.println(coffee.getPrice());
}
}
<実行結果>
360円です |
列挙された以外の要素(上記の例のCupSize.Large)は存在し得ないというところがEnumの良い点です。バグが入り込む余地が少なくなるわけです。(そういう意味では上記のdefault句は不要になります)また、スペルミスもIDEが教えてくれます。これがEnumではなく、単なるStringだとしたらこうはいきません。
さらに、Enumには、固有のフィールドやメソッド、コンストラクタを持たせることも可能です。
以下のサンプルコードを見て下さい。
package chap15;
public enum CupSize2 {
SHORT(240),
TALL(350),
GRANDE(470),
VENTI(590);
private final int price;
private CupSize2(int price) {
this.price = price;
}
int getPrice() {
return price;
}
}
ポイントは以下の3点です。
- 値の後ろに()を付けてその中にフィールドの値を書く
- フィールドをprivate finalとしてコンストラクタ内での初期化を強制する
- フィールドの値にアクセスすためのgetterメソッドを作る
使用例を以下Example11.javaで示します。
package chap15;
public class Example12 {
public static void main(String[] args) {
CupSize2[] cupsizes = CupSize2.values();
for (CupSize2 cupsize : cupsizes) {
System.out.println(cupsize + ":" + cupsize.getPrice());
}
}
}
<実行結果>
SHORT:240 TALL:350 GRANDE:470 VENTI:590 |
以上、駆け足でEnumを見てきました。Enumを使うことでプログラムが見やすくなる点もメリットですね。
<まとめ:隣の人に正しく説明できたらチェックを付けましょう>
□ 今現在を表すインスタンスはLocalDateTimeクラスのnowメソッドで取得できる
□ 日付の書式を指定するには、クラスDateTimeFormatterを使う
□ Enum型は複数の定数をひとつにまとめることが出来る型のことである
□ 定数と異なり、Enumには固有のフィールドやメソッド、コンストラクタを持たせることも可能である
まとめができたら、アウトプットとして演習問題にチャレンジしましょう。
【今回の復習Youtube】
JavaSE8の解説に戻る
最後までお読みいただきありがとうございました。