18

そのため、C++/C# ではフラグ列挙型を作成して複数の値を保持することができ、意味のある単一の整数をデータベースに格納することはもちろん簡単です。

Java には EnumSet があります。これは、メモリ内で列挙型を渡すのに非常に優れた方法のように見えますが、結合された EnumSet を格納用の整数に出力するにはどうすればよいでしょうか? これにアプローチする別の方法はありますか?

4

10 に答える 10

17

序数を EnumSet の表現として格納することはお勧めできません。序数は Enum クラスの定義の順序に依存します (関連する議論はこちら)。Enum 値の順序を変更したり、途中で新しい値を導入したりするリファクタリングによって、データベースが簡単に壊れる可能性があります。

個々の列挙値の安定した表現を導入する必要があります。これらは再び int 値にすることができ、 EnumSet に対して提案された方法で表すことができます。

列挙型はインターフェイスを実装できるため、安定した表現を列挙値に直接含めることができます (Adamski から適応):

interface Stable{
    int getStableId();
}
public enum X implements Stable {
    A(1), B(2);

    private int stableId;

    X(int id){
        this.stableId = id;
    }

    @Override public int getStableId() {
        return stableId;
    }
}

Adamski のコードから改作:

public <E extends Stable> int encode(EnumSet<E> set) {
  int ret = 0;

  for (E val : set) {
    ret |= (1 << val.getStableId());
  }

  return ret;
}
于 2010-02-04T12:36:18.463 に答える
12

列挙型が int に収まるようにする (つまり、値が 32 以下) 場合、各列挙型の序数の値を使用して独自の実装を展開します。例えば

public <E extends Enum<E>> int encode(EnumSet<E> set) {
  int ret = 0;

  for (E val : set) {
    // Bitwise-OR each ordinal value together to encode as single int.
    ret |= (1 << val.ordinal());
  }

  return ret;
}

public <E extends Enum<E>> EnumSet<E> decode(int encoded, Class<E> enumKlazz) {
  // First populate a look-up map of ordinal to Enum value.
  // This is fairly disgusting: Anyone know of a better approach?
  Map<Integer, E> ordinalMap = new HashMap<Integer, E>();
  for (E val : EnumSet.allOf(enumKlazz)) {
    ordinalMap.put(val.ordinal(), val);
  }

  EnumSet<E> ret= EnumSet.noneOf(enumKlazz);
  int ordinal = 0;

  // Now loop over encoded value by analysing each bit independently.
  // If the bit is set, determine which ordinal that corresponds to
  // (by also maintaining an ordinal counter) and use this to retrieve
  // the correct value from the look-up map.
  for (int i=1; i!=0; i <<= 1) {
    if ((i & encoded) != 0) {
      ret.add(ordinalMap.get(ordinal));
    }

    ++ordinal;
  }

  return ret;
}

免責事項:私はこれをテストしていません!

編集

Thomas がコメントで言及しているように、コード内の列挙型定義を変更すると、データベースのエンコーディングが破損するという点で、序数は不安定です (たとえば、既存の定義の途中に新しい列挙値を挿入した場合)。この問題を解決するための私のアプローチは、数値 ID (序数ではなく) と String 列挙値を含む、列挙ごとに「列挙型」テーブルを定義することです。私の Java アプリケーションが起動すると、DAO レイヤーが最初に行うことは、各 Enum テーブルをメモリに読み込み、次のことを行います。

  • データベース内のすべての String 列挙値が Java 定義と一致することを確認してください。
  • ID から enum へ、およびその逆の双方向マップを初期化します。これは、enum を永続化するたびに使用します (つまり、すべての「データ」テーブルは、文字列値を明示的に格納するのではなく、データベース固有の Enum ID を参照します)。 .

これは、私が上で説明した通常のアプローチよりもはるかにクリーンで堅牢な IMHO です。

于 2010-02-04T12:10:58.150 に答える
7
// From Adamski's answer
public static <E extends Enum<E>> int encode(EnumSet<E> set) {
    int ret = 0;

    for (E val : set) {
        ret |= 1 << val.ordinal();
    }

    return ret;
}

@SuppressWarnings("unchecked")
private static <E extends Enum<E>> EnumSet<E> decode(int code,
        Class<E> enumType) {
    try {
        E[] values = (E[]) enumType.getMethod("values").invoke(null);
        EnumSet<E> result = EnumSet.noneOf(enumType);
        while (code != 0) {
            int ordinal = Integer.numberOfTrailingZeros(code);
            code ^= Integer.lowestOneBit(code);
            result.add(values[ordinal]);
        }
        return result;
    } catch (IllegalAccessException ex) {
        // Shouldn't happen
        throw new RuntimeException(ex);
    } catch (InvocationTargetException ex) {
        // Probably a NullPointerException, caused by calling this method
        // from within E's initializer.
        throw (RuntimeException) ex.getCause();
    } catch (NoSuchMethodException ex) {
        // Shouldn't happen
        throw new RuntimeException(ex);
    }
}
于 2010-02-04T12:42:38.420 に答える
6

独自のライブラリを作成する代わりに、適切に管理されたライブラリを提案する人が誰もいないことに驚きました。上記の回答は的確で教育的ですが、コードをコピーして貼り付けることを人々に奨励するだけです (ほとんどの場合、クレジットを忘れます)。

ここに私の2セントがあります:

EnumSet<YourEnum> mySet = EnumSet.of(YourEnum.FIRST);
long vector = EnumUtils.generateBitVector(YourEnum.class, mySet);
EnumSet<YourEnum> sameSet = EnumUtils.processBitVector(YourEnum.class, vector);

https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/EnumUtils.htmlを参照してください

于 2018-01-29T21:23:03.470 に答える
5

RegularEnumSetEnum の <= 64 メンバーの実装であるのソースを見ると、以下が含まれていることがわかります。

/**
 * Bit vector representation of this set.  The 2^k bit indicates the
 * presence of universe[k] in this set.
 */
private long elements = 0L;

elements は、ビット位置が列挙序数と等しいビットマスクです。これはまさに必要なものです。ただし、この属性は、getter または setter を介して使用できるようにはなりません。これは、JumboEnumSet.

これは最も優れたソリューションの 1 つではありませんが、単純さと速度を求める場合は、elementsリフレクションを使用して属性を取得および設定する 2 つの静的ユーティリティ メソッドを作成できます。

私にとっては、列挙型の値を整数定数として保持する定数クラスをセットアップするだけで、どの列挙型にどのビットが割り当てられるかを確認できます。

于 2010-02-04T12:35:40.497 に答える
3

EnumSetを実装Serializableしますが、これを使用すると多くのオーバーヘッドが発生します(これは、IDの配列として記述され、BitSet予想どおりではなく、オブジェクトストリームヘッダーとして記述されます)。

于 2010-02-04T12:06:30.873 に答える
1

データベース内の序数の値の長所と短所についての議論に入ることなく - 私はここに与えられた質問への可能な答えを投稿しました: Enums の JPA マップ コレクション

PersistentEnumSetアイデアは、の実装を使用する新しいを作成することですが、 JPA にビットマスクをjava.util.RegularEnumSet提供します。elements

それは埋め込み可能で使用できます:

@Embeddable
public class InterestsSet extends PersistentEnumSet<InterestsEnum> {
  public InterestsSet() {
    super(InterestsEnum.class);
  }
}

そして、そのセットはエンティティで使用されます:

@Entity
public class MyEntity {
  // ...
  @Embedded
  private InterestsSet interests = new InterestsSet();
}

さらなるコメントについては、あちらの私の答えを参照してください。

于 2015-01-27T22:41:51.280 に答える