そのため、C++/C# ではフラグ列挙型を作成して複数の値を保持することができ、意味のある単一の整数をデータベースに格納することはもちろん簡単です。
Java には EnumSet があります。これは、メモリ内で列挙型を渡すのに非常に優れた方法のように見えますが、結合された EnumSet を格納用の整数に出力するにはどうすればよいでしょうか? これにアプローチする別の方法はありますか?
序数を 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;
}
列挙型が 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 テーブルをメモリに読み込み、次のことを行います。
これは、私が上で説明した通常のアプローチよりもはるかにクリーンで堅牢な IMHO です。
// 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);
}
}
独自のライブラリを作成する代わりに、適切に管理されたライブラリを提案する人が誰もいないことに驚きました。上記の回答は的確で教育的ですが、コードをコピーして貼り付けることを人々に奨励するだけです (ほとんどの場合、クレジットを忘れます)。
ここに私の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を参照してください
RegularEnumSet
Enum の <= 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 つの静的ユーティリティ メソッドを作成できます。
私にとっては、列挙型の値を整数定数として保持する定数クラスをセットアップするだけで、どの列挙型にどのビットが割り当てられるかを確認できます。
EnumSet
を実装Serializable
しますが、これを使用すると多くのオーバーヘッドが発生します(これは、IDの配列として記述され、BitSet
予想どおりではなく、オブジェクトストリームヘッダーとして記述されます)。
データベース内の序数の値の長所と短所についての議論に入ることなく - 私はここに与えられた質問への可能な答えを投稿しました: 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();
}
さらなるコメントについては、あちらの私の答えを参照してください。