Java Enumへの値の付加

1. 前書き

link:/a-guide-to-java-enums[Java _enum_ type]は、定数値を作成および使用するための言語サポートされた方法を提供します。 有限の値セットを定義することにより、_enum_は、_String_や_int_などの定数リテラル変数よりも型安全です。
*ただし、_enum_値は有効な識別子である必要があります*。慣例によりSCREAMING_SNAKE_CASEを使用することをお勧めします。
これらの制限を考えると、* enum_の値だけでは、人間が読める文字列または文字列以外の値*には適していません。
このチュートリアルでは、_enum_’sの機能をJavaクラスとして使用して、必要な値を付加します。

2. Java _Enum_をクラスとして使用

多くの場合、_enum_は単純な値のリストとして作成されます。 たとえば、周期表の最初の2行は、単純な_enum_です。
public enum Element {
    H, HE, LI, BE, B, C, N, O, F, NE
}
上記の構文を使用して、_Element_という名前の_enum_の静的な最終インスタンスを10個作成しました。 これは非常に効率的ですが、要素シンボルのみをキャプチャしました。 また、大文字の形式はJava定数に適していますが、通常のシンボルの書き方ではありません。
さらに、周期表要素の他のプロパティ(名前や原子量など)も欠落しています。
  • _enum_型はJavaで特別な動作をしますが、他のクラスと同様にコンストラクター、フィールド、およびメソッドを追加できます。 このため、必要な値を含めるように_enum_を強化できます。*

3. コンストラクターと最終フィールドの追加

要素名を追加することから始めましょう。 *コンストラクタを使用して、名前を_private_ _final_変数に設定します*:
public enum Element {
    H("Hydrogen"),
    HE("Helium"),
    // ...
    NE("Neon");

    public final String label;

    private Element(String label) {
        this.label = label;
    }
}
まず、宣言リストに特別な構文があることに気付きます。 これは、_enum_型に対してコンストラクターが呼び出される方法です。 * _enum_に_new_演算子を使用することは違法ですが、宣言リストでコンストラクター引数を渡すことができます。*
次に、インスタンス変数_label_を宣言します。 それについて注意すべきことがいくつかあります。
*まず、_name_の代わりに_label_ identifierを選択しました。 メンバーフィールド_name_は使用可能ですが、事前に定義された_Enum.name()_メソッドとの混乱を避けるために、_label_を選択しましょう。
次に、* label_フィールドは_final_です。 _enum_のフィールドは_final_である必要はありませんが、ほとんどの場合、ラベルを変更することは望ましくありません。
最後に、_label_フィールドはパブリックです。 したがって、ラベルに直接アクセスできます。
System.out.println(BE.label);
一方、フィールドは_private_にすることができ、_getLabel()_メソッドでアクセスできます。 簡潔にするために、この記事では引き続きパブリックフィールドスタイルを使用します。

4. Java _Enum_値の特定

Javaは、すべての_enum_型に対して_valueOf(String)_メソッドを提供します。 したがって、宣言された名前に基づいて_enum_値を常に取得できます。
assertSame(Element.LI, Element.valueOf("LI"));
ただし、ラベルフィールドで_enum_値を検索することもできます。 これを行うには、_static_メソッドを追加します。
public static Element valueOfLabel(String label) {
    for (Element e : values()) {
        if (e.label.equals(label)) {
            return e;
        }
    }
    return null;
}
静的な_valueOfLabel()_メソッドは、一致するものが見つかるまで_Element_ valuesを繰り返します。 一致が見つからない場合は、_null_を返します。 逆に、_null_を返す代わりに例外をスローすることもできます。
_valueOfLabel()_メソッドを使用した簡単な例を見てみましょう。
assertSame(Element.LI, Element.valueOfLabel("Lithium"));

5. ルックアップ値のキャッシュ

  • _Map_を使用してラベルをキャッシュすることで、_enum_値の反復を回避できます*。 これを行うには、_static final Map_を定義し、クラスがロードされるときにそれを取り込みます。

public enum Element {

    // ... enum values

    private static final Map<String, Element> BY_LABEL = new HashMap<>();

    static {
        for (Element e: values()) {
            BY_LABEL.put(e.label, e);
        }
    }

   // ... fields, constructor, methods

    public static Element valueOfLabel(String label) {
        return BY_LABEL.get(label);
    }
}
*キャッシュされた結果、_enum_の値は1回だけ反復され、_valueOfLabel()_メソッドは簡素化されます。
別の方法として、_valueOfLabel()_メソッドでキャッシュに最初にアクセスしたときに、キャッシュを遅延的に構築できます。 その場合、並行性の問題を防ぐために、マップアクセスを同期する必要があります。

6. 複数の値の付加

  • _Enum_コンストラクターは複数の値を受け入れることができます*。 説明のために、原子番号を_int_として、原子量を_float_として追加しましょう。

public enum Element {
    H("Hydrogen", 1, 1.008f),
    HE("Helium", 2, 4.0026f),
    // ...
    NE("Neon", 10, 20.180f);

    private static final Map<String, Element> BY_LABEL = new HashMap<>();
    private static final Map<Integer, Element> BY_ATOMIC_NUMBER = new HashMap<>();
    private static final Map<Float, Element> BY_ATOMIC_WEIGHT = new HashMap<>();

    static {
        for (Element e : values()) {
            BY_LABEL.put(e.label, e);
            BY_ATOMIC_NUMBER.put(e.atomicNumber, e);
            BY_ATOMIC_WEIGHT.put(e.atomicWeight, e);
        }
    }

    public final String label;
    public final int atomicNumber;
    public final float atomicWeight;

    private Element(String label, int atomicNumber, float atomicWeight) {
        this.label = label;
        this.atomicNumber = atomicNumber;
        this.atomicWeight = atomicWeight;
    }

    public static Element valueOfLabel(String label) {
        return BY_LABEL.get(label);
    }

    public static Element valueOfAtomicNumber(int number) {
        return BY_ATOMIC_NUMBER.get(number);
    }

    public static Element valueOfAtomicWeight(float weight) {
        return BY_ATOMIC_WEIGHT.get(weight);
    }
}
同様に、適切な場合の記号「He」、「Li」、「Be」など、必要な値を_enum_に追加できます。
さらに、操作を実行するメソッドを追加することで、計算値を_enum_に追加できます。

7. インターフェイスの制御

_enum_にフィールドとメソッドを追加した結果、パブリックインターフェイスが変更されました。 したがって、コアの_Enum_ _name()_および_valueOf()_メソッドを使用するコードは、新しいフィールドを認識しません。
  • static _valueOf()_メソッドは、Java言語によってすでに定義されています。 したがって、独自の_valueOf()_実装を提供することはできません。*

    同様に、* _ Enum.name()_メソッドは__finalなので、__weもそれをオーバーライドできません*。
    その結果、標準の_Enum_ APIを使用して追加のフィールドを利用する実用的な方法はありません。 代わりに、フィールドを公開するいくつかの異なる方法を見てみましょう。

* 7.1。 toString() *のオーバーライド

_toString()_をオーバーライドすることは、_name()_をオーバーライドする代わりになります。
@Override
public String toString() {
    return this.label;
}
デフォルトでは、_Enum.toString()_は_Enum.name()._と同じ値を返します

* 7.2。 インターフェイスの実装*

  • Javaの_enum_タイプはインターフェースを実装できます*。 このアプローチは_Enum_ APIほど一般的ではありませんが、インターフェイスは一般化に役立ちます。

    このインターフェースを考えてみましょう:
public interface Labeled {
    String label();
}
_Enum.name()_メソッドとの一貫性を保つため、_label()_メソッドには_get_プレフィックスがありません。
また、_valueOfLabel()_メソッドは_static_であるため、インターフェイスには含めません。
最後に、_enum_にインターフェイスを実装できます。
public enum Element implements Labeled {

    // ...

    @Override
    public String label() {
        return label;
    }

    // ...
}
このアプローチの利点の1つは、_enum_型だけでなく、_Labeled_インターフェイスを任意のクラスに適用できることです。 汎用の_Enum_ APIに依存する代わりに、よりコンテキスト固有のAPIが用意されました。

8. 結論

この記事では、Java _Enum_実装の多くの機能を調査しました。 コンストラクター、フィールド、およびメソッドを追加すると、_enum_がリテラル定数よりも多くのことができることがわかります。
いつものように、この記事の完全なソースコードはhttps://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-lang[Github]で見つけることができます。