【Spring Boot】【Java】定数宣言はfinal staticが良いのか、enumが良いのか

# これまでの記事紹介
|  | URL |
|:-----------|:------------|
| 第1回       | [【Spring Boot】個人的に推奨するディレクトリ／ファイル構成](https://qiita.com/rpc_tk/items/b60adb5e55206585d98a "【Spring Boot】個人的に推奨するディレクトリ／ファイル構成")        |

# はじめに
第1回記事の最後に[domain]-coreについて深堀りしていくと書きましたが、今回はその深掘りの第1弾です。
※[domain]-coreには定数、列挙型を配置するためです

皆さんはJavaで不変な値、いわゆる定数をどのように扱っていますか？
final static修飾子を付けた変数を使うのが良いのか、列挙型を使うのが良いのかを個人的な考えを書いていきます。

# 読者ターゲット
- Java の Web/API アプリケーション開発経験がある方

# 定数とは
定数はある意味で変数ですが、final修飾子を付与することで一度値を代入したら変更できない変数となり、更にstatic修飾子を付与することでクラス変数となり、インスタンス化せずとも直接アクセスできるようになります。
定数は一般的にfinal修飾子とstatic修飾子を付与したものを指します。

## 宣言
```java
public class HogeConstant {
  // 変数
  public String HOGE = "hoge";
  // 変数にfinal修飾子を付与すると再代入が禁止となり定数となる
  public final String FINAL_HOGE = "hoge";
  // 更にstatic修飾子を付与するとクラスをインスタンス化しなくてもアクセス可能となる
  public final static String FINAL_STATIC_HOGE = "hoge";
}
```
## 取得
```java
// HogeConstantクラスのインスタンス生成
HogeConstant constant = new HogeConstant();

// HogeConstantクラスのインスタンスから変数を取得
String hoge1 = constant.HOGE;
// 再代入OK（コンパイルエラー出ない
constant.HOGE = "fuga";

// HogeConstantクラスのインスタンスからfinal修飾子を付与した変数を取得
String hoge2 = constant.FINAL_HOGE;
// 再代入NG（final修飾子のためコンパイルエラー出る
constant.FINAL_HOGE = "fuga";

// HogeConstantクラスからfinal修飾子、static修飾子を付与した変数を直接取得
String hoge3 = HogeConstant.FINAL_STATIC_HOGE;
// 再代入NG（final修飾子のためコンパイルエラー出る
HogeConstant.FINAL_STATIC_HOGE = "fuga";
```

# 列挙型とは
classではなくenumを付与した特別な型です。複数の定数（列挙子）を一つの型で統一、一つの列挙子に対して複数の値が定義できます。public修飾子とstatic修飾子を付与して公開メソッドを作ることも可能です。

## 宣言
```java
// 曜日の列挙型（java.time.DayOfWeek列挙型ではありません
public enum DayOfWeek {

  // 曜日の列挙子を宣言、一つの列挙子に対してID、曜日名を定義
  SUNDAY(0, "日"),
  MONDAY(1, "月"),
  TUESDAY(2, "火"),
  WEDNESDAY(3, "水"),
  THURSDAY(4, "木"),
  FRIDAY(5, "金"),
  SATURDAY(6, "土"),
  NONE(-1, ""); // 不正値対策

  // 列挙子のID
  private int id;
  // 列挙子の曜日名
  private String label;

  // コンストラクタ
  private DayOfWeek(int id, String label) {
    this.id = id;
    this.label = label;
  }

  // 列挙子のID取得
  public int getId() {
    return id;
  }

  // 列挙子の曜日名取得
  public String getLabel() {
    return label;
  }

  // 列挙子のIDから該当する列挙を取得
  public static DayOfWeek of(int id) {
    return java.util.Arrays
      .stream(DayOfWeek.values())
      .filter(data -> data.getId() == id)
      .findFirst()
      .orElse(NONE); // 不正値対策
  }

  // 列挙子のIDから該当する列挙の曜日名を取得
  public static String getLabelName(int dayOfWeek) {
    return DayOfWeek.of(dayOfWeek).label;
  }
}
```

## 取得
```java
DayOfWeek week = DayOfWeek.of(0);
String label = DayOfWeek.getLabelName(0);
```

# 列挙型の優秀な点
### APIのリクエスト、レスポンス項目の型
APIのリクエスト、レスポンス項目の型として列挙型は非常に有用です。
OpenAPIのSwaggerUI(※)を使ってリクエスト、レスポンスを視覚化してみましょう。
※分からない方はhttps://swagger.io/tools/swagger-ui/

例として現在の「日付」と「曜日」を返すAPIを用意しました。
「曜日」は文字列で返却しているのですが列挙型にしています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3586238/5154a0d7-9259-800c-f6cf-99f9c5d012e7.png)

「Response」-「Example Value」→「Response」-「Schema」に切り替えると
「曜日」は列挙型を指すEnumとなっており、MONDAY ～ SUNDAYに限定されている文字列ということが分かると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3586238/731812a2-c0ac-393f-2c4d-7751ac7b9a30.png)

リクエストではクライアント側から列挙型に未定義の値が送られてくることを未然に防げますし、
レスポンスではクライアント側に返す値は列挙型に定義された値のみに限定できます。

### 公開メソッドを作れる
列挙型は前述したような公開メソッドを作ることができるので、末尾に"曜日"のように文字列を付与して返却するなどちょっとした機能拡張も可能です。

# 定数の優秀な点
定数はfinal static宣言のたった1行で済みます。
列挙側は機能が豊富な一方、定数と比べるとコード量はどうしても多くなります。
## 列挙型と定数のコード量の比較
### 列挙側の場合
#### 宣言
```Java
public enum DateFormatEnum {

  DATE_HYPHEN_FORMAT("yyyy-MM-dd");

  private String format;

  public String getFormat() {
    return format;
  }

  private DateFormatEnum(String format) {
    this.format = format;
  }
}
```
#### 取得
```Java
String format = DateFormatEnum.DATE_HYPHEN_FORMAT.getFormat();
```
### 定数の場合
#### 宣言
```Java
public class DateFormatConstant {
  public final static String DATE_HYPHEN_FORMAT = "yyyy-MM-dd";
}
```
#### 取得
```Java
String format = DateFormatConstant.DATE_HYPHEN_FORMAT;
```
このように列挙側の方がコード量が多いことが分かります。


# それぞれの用途
ではどのような使い分けが良いのか、私個人の所感ですが以下となります。

|  | 用途 | 例 |
|:-----------|:------------|:------------|
| 定数       | カテゴライズできない        | フォーマット、正規表現         |
| 列挙型     | カテゴライズできる。APIなどエンドポイントのリクエスト、レスポンス項目      | 曜日、性別、血液型、都道府県       |

といった使い分けをしております。

このカテゴライズできる、できないというのは曖昧な言い方なので
もっと厳密に言うと
**数値のID体系を付けられるものか、付けられないものか**
としています。

フォーマット、正規表現は数値のID付けられますか？
```Java
// 日付フォーマット
public final static String DATE_HYPHEN_FORMAT = "yyyy-MM-dd";
public final static String DATE_SLASH_FORMAT  = "yyyy/MM/dd";
// 正規表現
public final static String ZIPCODE_WITHOUT_HYPHEN_PATTERN = "^[0-9]{7}$";
public final static String ZIPCODE_WITH_HYPHEN_PATTERN    = "^[0-9]{3}-[0-9]{4}$";
```
日付フォーマットルールその1、その2などと付けることはできますが、付けたIDの用途はなく、コード量が多くなるため列挙型にするメリットが薄いです。

一方、曜日、性別、血液型、都道府県は数値のID付けられますよね。
※データベースのテーブルカラムに格納する値としても良く見かけると思います。

```Java
// 性別
1:男性、2:女性、3:選択なし
// 血液型
1:A型、2:B型、3:O型、4:AB型
```

このように数値のID体系を付けられる、付けられないかが定数と列挙型の使い分けのラインだと私は考えています。

# 最後に
定数と列挙型の使い分けについて語りましたが、あくまで私の所感であって正解ではありません。
使い分けで悩んでる方への一助になれば幸いです。