将EnumSet存储在数据库中?

Mal*_*ker 18 java flags

因此,在C++/C#中,您可以创建标记枚举以保存多个值,并且在数据库中存储单个有意义的整数当然是微不足道的.

在Java中你有EnumSets,它似乎是一种在内存中传递枚举的好方法,但是你如何将组合的EnumSet输出到一个整数来存储呢?还有另一种方法来解决这个问题吗?

Tho*_*ung 16

将序数存储为EnumSet的表示并不是一个好主意.序数取决于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;
    }
}
Run Code Online (Sandbox Code Playgroud)

改编自Adamski的代码:

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

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

  return ret;
}
Run Code Online (Sandbox Code Playgroud)

  • @finnw所以只有一些简单的规则,一切都很完美:1.不要改变枚举值的顺序.2.不要删除枚举值(不再使用)将它们标记为@Deprecated.3.除非存在@Deprecated枚举值,否则仅在末尾添加值,然后将其替换为新值.(忽略遗留枚举值的规则.遗憾的是,@ Deprecated已经被烧毁了.)太好了!</讽刺> (4认同)
  • @finnw假设这是一个好主意(它不是,但无论如何)你如何删除枚举值? (2认同)

Ada*_*ski 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;
}
Run Code Online (Sandbox Code Playgroud)

免责声明:我没有测试过这个!

编辑

正如托马斯在评论中提到的那样,序数是不稳定的,因为代码中对枚举定义的任何更改都会导致数据库中的编码损坏(例如,如果在现有定义的中间插入新的枚举值).我解决这个问题的方法是为每个枚举定义一个"枚举"表,其中包含一个数字ID(不是序数)和String枚举值.当我的Java应用程序启动时,DAO层所做的第一件事就是将每个Enum表读入内存,并且:

  • 验证数据库中的所有String枚举值是否与Java定义匹配.
  • 初始化ID的双向映射到枚举,反之亦然,然后每当我持久化枚举时使用它(换句话说,所有"数据"表引用数据库特定的枚举ID,而不是显式存储String值) .

这比我上面描述的序数方法更清晰/更强大的恕我直言.

  • 这不会给出你所追求的结果,你可能想说`ret | = 1 << val.ordinal()`:-) (2认同)

fin*_*nnw 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 你不需要通过反射调用`values()`.`Class`有一个方法`getEnumConstants`,它返回相同的. (2认同)

coo*_*ort 6

令我惊讶的是,没有人建议使用维护良好的库,而不是编写自己的库。上面的答案是正确的和有教育意义的,但它只是鼓励人们复制和粘贴代码(然后大多忘记学分)。

这是我的 2 美分:

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

https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/EnumUtils.html


rsp*_*rsp 5

如果您查看源代码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;
Run Code Online (Sandbox Code Playgroud)

元素是位掩码,位位置等于枚举,这正是您所需要的.但是,这个属性不能通过getter或setter使用,因为它不匹配的等效访问器JumboEnumSet.

它不是最好的解决方案之一,但如果简单性和速度是您所追求的,您可以创建2个静态实用程序方法,elements使用反射检索和设置属性.

对我来说,我可能只是设置一个常量类,将枚举值保存为整数常量,我可以确定哪个枚举被分配了什么位.