18

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

在 Java 中你有 EnumSets,这似乎是一种在内存中传递枚举的好方法,但是如何将组合的 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(不是序数)和字符串枚举值。当我的 Java 应用程序启动时,DAO 层做的第一件事就是将每个 Enum 表读入内存,然后:

  • 验证数据库中的所有 String 枚举值是否与 Java 定义匹配。
  • 将 ID 的双向映射初始化为枚举,反之亦然,然后我在持久化枚举时使用它(换句话说,所有“数据”表都引用特定于数据库的枚举 ID,而不是显式存储字符串值) .

恕我直言,这比我上面描述的顺序方法更清洁/更强大。

于 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

如果您查看源代码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.

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

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

于 2010-02-04T12:35:40.497 回答
3

EnumSetimplements Serializable,但是如果你使用它会有很多开销(它被写成一个 ID 数组,而不是BitSet你可能期望的 a ,加上对象流标头。)

于 2010-02-04T12:06:30.873 回答
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);
于 2017-05-02T22:49:00.850 回答
1

使用答案中给出的方法,可以将整数转换为 EnumSet,反之亦然。但我发现这通常容易出错。尤其是当您得到负值时,因为 java 只有带符号的 int 和 long。因此,如果您计划对所有枚举集进行此类转换,您可能希望使用已经支持此的数据结构。我已经创建了这样一个数据结构,它可以像 BitSet 或 EnumSet 一样使用,但它也有诸如 toLong() 和 toBitSet() 之类的方法。请注意,这需要 Java 8 或更高版本。

这是链接:http ://claude-martin.ch/enumbitset/

于 2014-04-26T17:35:12.663 回答
1

在不讨论数据库中序数值的优缺点的情况下 - 我在这里发布了对给定问题的可能答案: JPA map collection of Enums

这个想法是创建一个新PersistentEnumSet的,它使用 的实现java.util.RegularEnumSet,但elements向 JPA 提供位掩码。

那一个可以用于嵌入:

@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 回答
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);
    }
}
于 2015-08-26T11:18:17.330 回答