新人エンジニア研修で知っておきたい日付/時刻と列挙型の使い方

※この章は研修時間の都合により丸ごとスキップすることがあります。

なぜ、日付/時刻と列挙型の理解が重要なのか、その理由。

この記事では、弊社の新人エンジニア研修の参考に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);
    }
}

<実行結果>

2023-12-03T08:03:32.112978700

結果は「西暦-月-日T時:分:秒」の形で表示されました。ちなみに上記の“T”は単なる区切り文字で意味はありません。

インスタンスから日付だけを取り出すには、toLocalDate()メソッドを、時刻だけを取り出すにはtoLocalTime()メソッドを使います。

また、年だけ、月だけ、日だけ、、、のように一部の要素を取り出すには以下のようにgetで始まるメソッドを使用します。

package chap15;

import java.time.LocalDateTime;

public class Example02 {

	public static void main(String[] args) {

		LocalDateTime date = LocalDateTime.now();
		System.out.println(date.toLocalDate());
		System.out.println(date.toLocalTime());
		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() + "ナノ秒");
	}
}

<実行結果の例>

2023-12-03
08:00:05.939202800
2023年
DECEMBER月
3日
SUNDAY曜日
8時
0分
5秒
939202800ナノ秒

ところで、ここで出てきた「FEBRUARY」、「WEDNESDAY」といった文字列は何でしょうか?

実はこれらは列挙型と呼ばれるJavaの型の一つです。列挙型は複数の定数をひとつにまとめておくことができる型です。月はJANUARYから始まってNOVEMBERまでの12個しかありません。曜日はMONDAYから始まってSUNDAYまでの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.StringとLocalDateTimeの相互変換

文字列とLocalDateTimeの相互変換はこの後、JavaWebアプリケーションでJavaからデータベースを活用する際に必要になることがあります。例えば、データベースに日時を文字列型(VARCHAR型)などで格納しておき、Javaで取得した上で、LocalDateTimeに変換した上で、日時の処理ができると便利ですね。

JavaでString型とLocalDateTime型を相互に変換する方法を示します。

以下のサンプルコードでは、StringからLocalDateTimeへの変換を行っています。JavaではDateTimeFormatterクラスを使用して、特定のフォーマットの日付文字列をLocalDateTimeオブジェクトに変換できます。

サンプルコードExample05.javaを見てください。

package chap15;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class Example05 {
	public static void main(String[] args) {
		String dateTimeString = "2023-12-03T10:15:30";
		DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
		LocalDateTime dateTime = LocalDateTime.parse(dateTimeString, formatter);
		System.out.println("LocalDateTime: " + dateTime);
	}
}

<実行結果の例>

LocalDateTime: 2023-12-03T10:15:30

上記の例では、ISOローカル日付時刻フォーマット(yyyy-MM-ddTHH:mm:ss)を使用していますが、必要に応じて異なるフォーマットを指定できます。

次に、以下のExample06.javaでは、LocalDateTimeからStringへの変換を行っています。この場合は単にtoString()メソッドを使うだけなので簡単です。

package chap15;

import java.time.LocalDateTime;

public class Example06 {

	public static void main(String[] args) {
		System.out.println("文字列: " + LocalDateTime.now().toString());
	}
}

<実行結果の例>

文字列: 2023-12-03T10:05:35.7636081

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

このようにたくさんのクラス、メソッドがあります。とてもこの新人エンジニア研修の期間では紹介しきれません。

皆さんは必要に応じて調べて使ってください。

2.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点です。

  1. 値の後ろに()を付けてその中にフィールドの値を書く
  2. フィールドをprivate finalとしてコンストラクタ内での初期化を強制する
  3. フィールドの値にアクセスすためのgetter()メソッドを作る

使用例を以下Example12.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には固有のフィールドやメソッド、コンストラクタを持たせることも可能である

まとめができたら、アウトプットとして演習問題にチャレンジしましょう。

問題15.日付/時刻と列挙型 - セイコンサルティンググループ

1~5は、java 8 で新たに追加されたjava.timeパッケージに関連した問題、6はEnum(列挙型)に関する問題です。

【今回の復習Youtube】

059-APIの利用-現在の日付を求める

060-APIの利用-当月の初日の日付を求める

061-APIの利用-当月の末日の日付を求める

062-APIの利用-任意の日付を指定する

063-APIの利用-日付の書式を指定する

064-APIの利用-任意の時刻を指定する

065-APIの利用-時刻を比較する

052-enum型-enum型のサンプル

JavaSE8の解説に戻る

最後までお読みいただきありがとうございました。