因此,在C ++ / C#中,您可以创建标记枚举以保存多个值,并且在数据库中存储单个有意义的整数当然是微不足道的。
在Java中,你有EnumSets,这似乎是一种在内存中传递枚举的好方法,但是如何将组合的EnumSet输出到整数进行存储?还有另一种方法可以解决这个问题吗?
答案 0 :(得分:16)
将序数存储为EnumSet的表示并不是一个好主意。序数取决于Enum类(a related discussion is here)中定义的顺序。您的数据库可能很容易通过重构来破坏,该重构会更改枚举值的顺序或在中间引入新的值。
您必须引入单个枚举值的稳定表示。这些可以再次为int值,并在proposed way for the 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;
}
答案 1 :(得分:12)
将你的枚举拟合到一个int(即有&lt; = 32值)我将使用每个枚举的序数值来推动我自己的实现; e.g。
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表读入内存并且:
这比我上面描述的序数方法更清晰/更强大的恕我直言。
答案 2 :(得分: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);
}
}
答案 3 :(得分:5)
如果您查看RegularEnumSet
的来源,这是Enum的&lt; = 64成员的实现,您会看到它包含:
/**
* Bit vector representation of this set. The 2^k bit indicates the
* presence of universe[k] in this set.
*/
private long elements = 0L;
元素是位掩码,位位置等于枚举,这正是您所需要的。但是,此属性不能通过getter或setter使用,因为它与JumboEnumSet
的等效访问器不匹配。
它不是最好的解决方案之一,但如果你想要的是简单性和速度,你可以创建2个静态实用程序方法,使用反射检索和设置elements
属性。
对我来说,我可能只是设置一个常量类,将枚举值保存为整数常量,我可以确定哪个枚举被分配了什么位。
答案 4 :(得分:4)
让我感到惊讶的是,没有人建议维护良好的图书馆,而不是写自己的图书馆。上面的答案是现场和教育,但它只是鼓励人们复制和粘贴代码(然后大多忘记学分)。
这是我的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
答案 5 :(得分:3)
EnumSet
实现了Serializable
,但是如果你使用它会有很多开销(它被写成一个ID数组,而不是你想象中的BitSet
,加上对象流标题。)
答案 6 :(得分:2)
这是我发现有用的旧帖子,但是使用Java 8或更新版本我已经将@finnw发布的解决方案改编为此界面:
public interface BitMaskable {
int getBitMaskOrdinal();
static int bitMaskValue(Set<? extends BitMaskable> set) {
int mask = 0;
for (BitMaskable val : set) {
mask |= (1 << val.getBitMaskOrdinal());
}
return mask;
}
static <E extends Enum<E> & BitMaskable> Set<E> valueOfBitMask(int mask, Class<E> enumType) {
E[] values = enumType.getEnumConstants();
EnumSet<E> result = EnumSet.noneOf(enumType);
Map<Integer, E> ordinalCache = null;
while (mask != 0) {
int ordinal = Integer.numberOfTrailingZeros(mask);
mask ^= Integer.lowestOneBit(mask);
E value = null;
if (ordinalCache != null) {
value = ordinalCache.get(ordinal);
}
if (value == null) {
for (E e : values) {
if (e.getBitMaskOrdinal() == ordinal) {
value = e;
break;
}
// if there are more values to decode and e has a higher
// ordinal than what we've seen, cache that for later
if (mask != 0 && e.getBitMaskOrdinal() > ordinal) {
if (ordinalCache == null) {
ordinalCache = new HashMap<>(values.length);
}
ordinalCache.put(e.getBitMaskOrdinal(), e);
}
}
}
if (value != null) {
result.add(value);
}
}
return result;
}
}
这样的枚举的用法(注意 bmOrdinal
值与内置枚举序号值无关):
public enum BitMaskEnum implements BitMaskable {
A(0),
B(2),
C(1),
D(3);
private int bmOrdinal;
private BitMaskEnum(int bmOrdinal) {
this.bmOrdinal = bmOrdinal;
}
@Override
public int getBitMaskOrdinal() {
return bmOrdinal;
}
}
然后沿着这些方向:
// encode as bit mask; result == 5
int result = BitMaskable.bitMaskValue(EnumSet.of(BitMaskEnum.A, BitMaskEnum.B));
// decode into set; result contains A & B
Set<BitMaskEnum> result = BitMaskable.valueOfBitMask(5, BitMaskEnum.class);
答案 7 :(得分:1)
使用答案中给出的方法,可以将整数转换为EnumSet,反之亦然。但我发现这通常容易出错。特别是当你得到负值时,因为java只签署了int和long。因此,如果您计划对所有枚举集进行此类转换,则可能需要使用已支持此功能的数据结构。我创建了这样一个数据结构,可以像BitSet或EnumSet一样使用,但它也有toLong()和toBitSet()等方法。请注意,这需要Java 8或更高版本。
答案 8 :(得分:1)
没有进入关于数据库中序数值的利弊的辩论 - 我在这里发布了对给定问题的可能答案: JPA map collection of Enums
我们的想法是创建一个使用PersistentEnumSet
实现的新java.util.RegularEnumSet
,但为JPA提供elements
位掩码。
那个可以用于嵌入式:
@Embeddable
public class InterestsSet extends PersistentEnumSet<InterestsEnum> {
public InterestsSet() {
super(InterestsEnum.class);
}
}
该集合用于实体:
@Entity
public class MyEntity {
// ...
@Embedded
private InterestsSet interests = new InterestsSet();
}
有关进一步的评论,请参阅我的答案。
答案 9 :(得分:1)
我对finnw的代码做了一些更改,因此它适用于最多包含64个项目的枚举。
// From Adamski's answer
public static <E extends Enum<E>> long encode(EnumSet<E> set) {
long ret = 0;
for (E val : set) {
ret |= 1L << val.ordinal();
}
return ret;
}
@SuppressWarnings("unchecked")
public static <E extends Enum<E>> EnumSet<E> decode(long code,
Class<E> enumType) {
try {
E[] values = (E[]) enumType.getMethod("values").invoke(null);
EnumSet<E> result = EnumSet.noneOf(enumType);
while (code != 0) {
int ordinal = Long.numberOfTrailingZeros(code);
code ^= Long.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);
}
}