1. 概要

Java列挙型は、定数値を作成および使用するための言語サポートの方法を提供します。 有限の値のセットを定義することにより、列挙型は、Stringintなどの定数リテラル変数よりもタイプセーフです。

ただし、列挙値は有効な識別子である必要があります。慣例により、SCREAMING_SNAKE_CASEを使用することをお勧めします。

これらの制限を考えると、列挙値だけでは、人間が読める形式の文字列や文字列以外の値には適していません。

このチュートリアルでは、 enum 機能をJavaクラスとして使用して、必要な値を付加します。

2. Java Enumをクラスとして使用する

単純な値のリストとして列挙型を作成することがよくあります。 たとえば、周期表の最初の2行を単純な列挙型として次に示します。

public enum Element {
    H, HE, LI, BE, B, C, N, O, F, NE
}

上記の構文を使用して、Elementという名前のenumの静的な最終インスタンスを10個作成しました。 これは非常に効率的ですが、要素シンボルのみをキャプチャしました。 Java定数には大文字の形式が適していますが、通常の記号の記述方法とは異なります。

さらに、名前や原子量など、周期表要素の他のプロパティもありません。

enum 型はJavaで特別な動作をしますが、他のクラスと同じようにコンストラクター、フィールド、およびメソッドを追加できます。 このため、列挙型を拡張して、必要な値を含めることができます。

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

要素名を追加することから始めましょう。

コンストラクターを使用して名前を最終変数に設定します

public enum Element {
    H("Hydrogen"),
    HE("Helium"),
    // ...
    NE("Neon");

    public final String label;

    private Element(String label) {
        this.label = label;
    }
}

まず、宣言リストに特別な構文があることに気づきます。 これは、enumタイプに対してコンストラクターが呼び出される方法です。 列挙型にnew演算子を使用することは違法ですが、宣言リストでコンストラクター引数を渡すことができます。

次に、インスタンス変数labelを宣言します。 それについて注意すべきことがいくつかあります。

まず、nameの代わりにlabel識別子を選択しました。 メンバーフィールドnameは使用できますが、事前定義された Enum.name()メソッドとの混同を避けるために、labelを選択しましょう。

2番、 ラベルフィールドは最終です。 列挙型のフィールドは最終である必要はありませんが、ほとんどの場合、ラベルを変更したくありません。 の精神で列挙型値が一定であるため、これは理にかなっています。

最後に、 label フィールドは公開されているため、ラベルに直接アクセスできます。

System.out.println(BE.label);

一方、フィールドは private で、 getLabel()メソッドでアクセスできます。 簡潔にするために、この記事では引き続きパブリックフィールドスタイルを使用します。

4. Java Enum値の検索

Javaは、すべての列挙型タイプに 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値を繰り返します。 一致するものが見つからない場合は、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);
    }
}

キャッシュされた結果、列挙値は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に追加できます。

さらに、操作を実行するメソッドを追加することで、計算値を列挙型に追加できます。

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

列挙型にフィールドとメソッドを追加した結果、パブリックインターフェイスが変更されました。 したがって、コア Enum name()および valueOf()メソッドを使用するコードは、新しいフィールドを認識しません。

static valueOf()メソッドはJava言語ですでに定義されているため、独自の valueOf()実装を提供することはできません。

同様に、 Enum.name()メソッドはfinalであるため、オーバーライドすることもできません。

その結果、標準の EnumAPIを使用して追加のフィールドを利用する実用的な方法はありません。 代わりに、フィールドを公開するためのいくつかの異なる方法を見てみましょう。

7.1. toString()をオーバーライドする

toString()をオーバーライドすることは、 name()をオーバーライドする代わりになる場合があります。

@Override 
public String toString() { 
    return this.label; 
}

デフォルトでは、 Enum.toString() Enum.name()。と同じ値を返します。

7.2. インターフェイスの実装

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

このインターフェースについて考えてみましょう。

public interface Labeled {
    String label();
}

Enum.name()メソッドとの一貫性を保つために、 label()メソッドにはgetプレフィックスがありません。

また、 valueOfLabel()メソッドは static であるため、インターフェイスには含めません。

最後に、列挙型にインターフェイスを実装できます。

public enum Element implements Labeled {

    // ...

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

    // ...
}

このアプローチの利点の1つは、 Labeled インターフェイスを、 enum タイプだけでなく、任意のクラスに適用できることです。 一般的なEnum APIに依存する代わりに、よりコンテキスト固有のAPIを使用できるようになりました。

8. 結論

この記事では、Java Enum実装の多くの機能について説明しました。 コンストラクター、フィールド、およびメソッドを追加することにより、enumがリテラル定数よりもはるかに多くのことを実行できることがわかります。

いつものように、この記事の完全なソースコードは、GitHubにあります。