9

私は何度もこの問題に直面したようで、間違ったツリーを吠えているだけなのかコミュニティに尋ねたかった. 基本的に、私の質問は次のように要約できます。値が重要な列挙型 (Java の場合) がある場合、列挙型を使用する必要がありますか、それともより良い方法がありますか?列挙型を使用する場合はどうすればよいですか?ルックアップを逆にする最良の方法は何ですか?

これが例です。特定の月と年を表す Bean を作成したいとします。次のようなものを作成する場合があります。

public interface MonthAndYear {
    Month getMonth();
    void setMonth(Month month);
    int getYear();
    void setYear(int year);
}

ここでは、月を Month という別のクラスとして保存しているため、タイプ セーフになっています。int だけを入力すると、誰でも 13、5,643、または -100 を数値として渡すことができ、コンパイル時にそれをチェックする方法がなくなります。列挙型として実装する月を入れるように制限しています。

public enum Month {
    JANUARY,
    FEBRUARY,
    MARCH,
    APRIL,
    MAY,
    JUNE,
    JULY,
    AUGUST,
    SEPTEMBER,
    OCTOBER,
    NOVEMBER,
    DECEMBER;
}

ここで、書き込みたいバックエンド データベースがあり、整数形式のみを受け入れるとします。これを行う標準的な方法は次のようです。

public enum Month {
    JANUARY(1),
    FEBRUARY(2),
    MARCH(3),
    APRIL(4),
    MAY(5),
    JUNE(6),
    JULY(7),
    AUGUST(8),
    SEPTEMBER(9),
    OCTOBER(10),
    NOVEMBER(11),
    DECEMBER(12);

    private int monthNum;
    public Month(int monthNum) {
        this.monthNum = monthNum;
    }

    public getMonthNum() {
        return monthNum;
    }
}

かなり簡単ですが、これらの値をデータベースから読み取るだけでなく、書き込む場合はどうなりますか? int を取り、それぞれの Month オブジェクトを返す enum 内の case ステートメントを使用して、静的関数を実装できます。しかし、これは、何かを変更した場合、この関数とコンストラクターの引数を変更する必要があることを意味します - 2 つの場所で変更します。これが私がやってきたことです。まず、リバーシブル マップ クラスを次のように作成しました。

public class ReversibleHashMap<K,V> extends java.util.HashMap<K,V> {
    private java.util.HashMap<V,K> reverseMap;

    public ReversibleHashMap() {
        super();
        reverseMap = new java.util.HashMap<V,K>();
    }

    @Override
    public V put(K k, V v) {
        reverseMap.put(v, k);
        return super.put(k,v);
    }

    public K reverseGet(V v) {
        return reverseMap.get(v);
    }
}

次に、コンストラクターメソッドの代わりに列挙型内にこれを実装しました。

public enum Month {
    JANUARY,
    FEBRUARY,
    MARCH,
    APRIL,
    MAY,
    JUNE,
    JULY,
    AUGUST,
    SEPTEMBER,
    OCTOBER,
    NOVEMBER,
    DECEMBER;

    private static ReversibleHashMap<java.lang.Integer,Month> monthNumMap;

    static {
        monthNumMap = new ReversibleHashMap<java.lang.Integer,Month>();
        monthNumMap.put(new java.lang.Integer(1),JANUARY);
        monthNumMap.put(new java.lang.Integer(2),FEBRUARY);
        monthNumMap.put(new java.lang.Integer(3),MARCH);
        monthNumMap.put(new java.lang.Integer(4),APRIL);
        monthNumMap.put(new java.lang.Integer(5),MAY);
        monthNumMap.put(new java.lang.Integer(6),JUNE);
        monthNumMap.put(new java.lang.Integer(7),JULY);
        monthNumMap.put(new java.lang.Integer(8),AUGUST);
        monthNumMap.put(new java.lang.Integer(9),SEPTEMBER);
        monthNumMap.put(new java.lang.Integer(10),OCTOBER);
        monthNumMap.put(new java.lang.Integer(11),NOVEMBER);
        monthNumMap.put(new java.lang.Integer(12),DECEMBER);
    }

    public int getMonthNum() {
        return monthNumMap.reverseGet(this);
    }

    public static Month fromInt(int monthNum) {
        return monthNumMap.get(new java.lang.Integer(monthNum));
    }
}

これで、私が望むすべてのことができますが、それでも間違っているように見えます。「列挙に意味のある内部値がある場合は、代わりに定数を使用する必要があります」と人々は私に提案しました。ただし、そのアプローチが私が探しているタイプセーフをどのように提供するかはわかりません。しかし、私が開発した方法は非常に複雑に思えます。この種のことを行う標準的な方法はありますか?

PS: 政府が新しい月を追加する可能性はかなり低いことはわかっていますが、全体像を考えてみてください。enum には多くの用途があります。

4

4 に答える 4

11

これは非常に一般的なパターンであり、列挙型では問題ありませんが、もっと簡単に実装できます。Month「リバーシブル マップ」は必要ありません。コンストラクターで月番号を使用するバージョンは、 from からへの移動に適していintます。しかし、他の方法もそれほど難しくありません。

public enum Month {
    JANUARY(1),
    FEBRUARY(2),
    MARCH(3),
    APRIL(4),
    MAY(5),
    JUNE(6),
    JULY(7),
    AUGUST(8),
    SEPTEMBER(9),
    OCTOBER(10),
    NOVEMBER(11),
    DECEMBER(12);

    private static final Map<Integer, Month> numberToMonthMap;

    private final int monthNum;

    static {
        numberToMonthMap = new HashMap<Integer, Month>();
        for (Month month : EnumSet.allOf(Month.class)) {
            numberToMonthMap.put(month.getMonthNum(), month);
        }
    }

    private Month(int monthNum) {
        this.monthNum = monthNum;
    }

    public int getMonthNum() {
        return monthNum;
    }

    public static Month fromMonthNum(int value) {
        Month ret = numberToMonthMap.get(value);
        if (ret == null) {
            throw new IllegalArgumentException(); // Or just return null
        }
        return ret;
    }
}

1 から N になることがわかっている数値の特定のケースでは、単純に配列を使用できます。呼び出しのたびに新しい配列が作成されるMonth.values()[value - 1]のを防ぐために、戻り値を取得またはキャッシュします。Month.values()(そして、cletus が言うように、getMonthNum単に戻ることができordinal() + 1ます。)

ただし、値が乱れたり、まばらに分布している可能性がある、より一般的なケースでは、上記のパターンに注意する価値があります。

すべての列挙値が作成された後に静的初期化子が実行されることに注意することが重要です。書くだけでいいよ

numberToMonthMap.put(monthNum, this);

コンストラクターで の静的変数初期化子を追加しますnumberToMonthMapが、それは機能しません-NullReferenceExceptionまだ存在しないマップに値を入れようとするため、すぐに取得できます:(

于 2009-10-17T19:10:05.723 に答える
4

これを行う簡単な方法があります。すべての列挙型には、そのordinal()番号を返すメソッドがあります (ゼロから始まります)。

public enum Month {
  JANUARY,
  FEBRUARY,
  MARCH,
  APRIL,
  MAY,
  JUNE,
  JULY,
  AUGUST,
  SEPTEMBER,
  OCTOBER,
  NOVEMBER,
  DECEMBER;

  public Month previous() {
    int prev = ordinal() - 1;
    if (prev < 0) {
      prev += values().length;
    }
    return values()[prev];
  }

  public Month next() {
    int next = ordinal() + 1;
    if (next >= values().length) {
      next = 0;
    }
    return values()[next];
  }
}

これをデータベースに保存する方法については、使用している永続化フレームワーク (存在する場合) によって異なります。JPA/Hibernate には、数値 (序数) または名前のいずれかで enum 値をマッピングするオプションがあります。月はおそらく変化しないものと見なすことができるので、序数を使用してください。特定の値を取得するには:

Month.values()[ordinalNumber];
于 2009-10-17T19:06:34.560 に答える
3

ここでの回答では、おそらく群れに遅れをとっていますが、もう少し簡単に実装する傾向があります。「Enum」には values() メソッドがあることを忘れないでください。

public static Month parse(int num)
{
  for(Month value : values())
  {
    if (value.monthNum == num)
    {
      return value;
    }
  }
  return null; //or throw exception if you're of that mindset
}
于 2010-09-27T14:51:19.507 に答える
1

この種のことには ordinal() を使用しないでください。月のサンプルでは機能しますが (拡張されないため)、Java の列挙型の良い点の 1 つは、拡張できるように設計されていることです。物を壊さずに。ordinal() に依存し始めると、途中で値を追加すると壊れます。

Jon Skeet が示唆するようにそれを行います (私がこれを書いている間に彼はそれを書きました) が、内部数値表現が 0 から 20 (または何か) の明確に定義された範囲内にある場合、おそらく HashMap を使用せずに導入しますint のオートボクシングではなく、通常の配列 (Month[12] など) を使用しますが、どちらも問題ありません (Jon は後でこの提案を含めるように投稿を変更しました)。

編集:自然な順序(ソートされた月など)があるいくつかの列挙型の場合、 ordinal() はおそらく安全に使用できます。誰かが列挙型の順序を変更する可能性がある場合に、それを持続する場合に遭遇するリスクがある問題が発生します。次のように: "enum { MALE, FEMALE }" は "enum {UNKNOWN, FEMALE, MALE}" になります。将来誰かがあなたが序数に依存していることを知らずにプログラムを拡張するときです。

私がちょうど書いていたのと同じことを書くためにジョンに+1を与えます。

于 2009-10-17T19:14:44.580 に答える