【问题标题】:Implementing a bitfield using java enums使用 java 枚举实现位域
【发布时间】:2011-07-17 19:37:21
【问题描述】:

我维护着一个大型文档存档,并且我经常使用位域来记录我的文档在处理期间或验证时的状态。我的遗留代码只是使用静态 int 常量,例如:

static int DOCUMENT_STATUS_NO_STATE = 0
static int DOCUMENT_STATUS_OK = 1
static int DOCUMENT_STATUS_NO_TIF_FILE = 2
static int DOCUMENT_STATUS_NO_PDF_FILE = 4

通过设置适当的标志,可以很容易地指示文档所处的状态。例如:

status = DOCUMENT_STATUS_NO_TIF_FILE | DOCUMENT_STATUS_NO_PDF_FILE;

由于使用静态常量的方法是不好的做法,并且因为我想改进代码,所以我希望使用 Enums 来实现相同的效果。有一些要求,其中之一是需要将状态作为数字类型保存到数据库中。因此需要将枚举常量转换为数值。以下是我的第一种方法,我想知道这是否是正确的方法?

class DocumentStatus{

    public enum StatusFlag {

        DOCUMENT_STATUS_NOT_DEFINED(1<<0),
        DOCUMENT_STATUS_OK(1<<1), 
        DOCUMENT_STATUS_MISSING_TID_DIR(1<<2),
        DOCUMENT_STATUS_MISSING_TIF_FILE(1<<3),
        DOCUMENT_STATUS_MISSING_PDF_FILE(1<<4),
        DOCUMENT_STATUS_MISSING_OCR_FILE(1<<5),
        DOCUMENT_STATUS_PAGE_COUNT_TIF(1<<6),
        DOCUMENT_STATUS_PAGE_COUNT_PDF(1<<7),
        DOCUMENT_STATUS_UNAVAILABLE(1<<8);


        private final long statusFlagValue;

        StatusFlag(long statusFlagValue) {
            this.statusFlagValue = statusFlagValue;
        }

        public long getStatusFlagValue(){
            return statusFlagValue;
        } 

       }


    /**
     * Translates a numeric status code into a Set of StatusFlag enums
     * @param numeric statusValue 
     * @return EnumSet representing a documents status
     */
    public EnumSet<StatusFlag> getStatusFlags(long statusValue) {
        EnumSet statusFlags = EnumSet.noneOf(StatusFlag.class);
        StatusFlag.each { statusFlag -> 
            long flagValue = statusFlag.statusFlagValue
            if ( (flagValue&statusValue ) == flagValue ) {
               statusFlags.add(statusFlag);
            }
        }
        return statusFlags;
    }


    /**
     * Translates a set of StatusFlag enums into a numeric status code
     * @param Set if statusFlags
     * @return numeric representation of the document status 
     */
    public long getStatusValue(Set<StatusFlag> flags) {
        long value=0;
        flags.each { statusFlag -> 
            value|=statusFlag.getStatusFlagValue() 
        }
        return value;
    }

     public static void main(String[] args) {

        DocumentStatus ds = new DocumentStatus();
        Set statusFlags = EnumSet.of(
            StatusFlag.DOCUMENT_STATUS_OK,
            StatusFlag.DOCUMENT_STATUS_UNAVAILABLE);

        assert ds.getStatusValue( statusFlags )==258 // 0000.0001|0000.0010

        long numericStatusCode = 56;
        statusFlags = ds.getStatusFlags(numericStatusCode);

        assert !statusFlags.contains(StatusFlag.DOCUMENT_STATUS_OK);
        assert statusFlags.contains(StatusFlag.DOCUMENT_STATUS_MISSING_TIF_FILE);
        assert statusFlags.contains(StatusFlag.DOCUMENT_STATUS_MISSING_PDF_FILE);
        assert statusFlags.contains(StatusFlag.DOCUMENT_STATUS_MISSING_OCR_FILE);

    }

}

【问题讨论】:

  • 为什么不能写:static int DOCUMENT_STATUS_NO_PDF_FILE = 1
  • @trashgod 链接似乎已死。 here 是另一个。
  • @SeyedJalalHosseini:另见第 32 项:使用 EnumSet 代替位域,引用 here
  • @trashgod 每次编写 EnumSet 时,您都会创建一个放在堆上而不是堆栈上的对象if (universe.length &lt;= 64) return new RegularEnumSet&lt;&gt;(elementType, universe);。虽然它是使用本机的小对象,但它仍然是一个对象。这仅在您的目标是零垃圾和/或 gc blips 时才重要。在高性能循环中,您不想调用 EnumSet.of 而更喜欢 longs... 等等。但这是一个极端的边缘案例。

标签: java enums enumset


【解决方案1】:

不要给你的枚举值。使用EnumSet 将它们组合起来,并在持久化时使用Enum.ordinal() 以转换为单个整数/从单个整数转换。在从整数重构集合时,您可能还会发现 Class.getEnumConstants() 很有用。

【讨论】:

  • 为什么给你的枚举值不好?
  • 因为他们已经有了可以用来解决这个问题的值。
  • 我相信 OP 需要与旧行为兼容的值。 ordinal() 应始终被视为瞬态而不是序列化,因为如果有人重新排序定义,兼容性将中断。
  • 依赖ordinal() 真是个糟糕的主意。
【解决方案2】:

你的方法正是这样做的。

【讨论】:

  • 在枚举值中存储额外数据是没有意义的,因为ordinal() 已经这样做了。
  • @OrangeDog,我不同意。如果您重构枚举,ordinal() 可以更改。考虑到ordinal 表示每个元素在枚举中的位置。但是,如果您存储额外的数据,您可以自行控制。
【解决方案3】:

您可以简单地使用内部ordinal() 值来计算此值,而不是定义构造函数参数。

public enum StatusFlag {

    DOCUMENT_STATUS_NOT_DEFINED,
    DOCUMENT_STATUS_OK, 
    DOCUMENT_STATUS_MISSING_TID_DIR,
    DOCUMENT_STATUS_MISSING_TIF_FILE,
    DOCUMENT_STATUS_MISSING_PDF_FILE,
    DOCUMENT_STATUS_MISSING_OCR_FILE,
    DOCUMENT_STATUS_PAGE_COUNT_TIF,
    DOCUMENT_STATUS_PAGE_COUNT_PDF,
    DOCUMENT_STATUS_UNAVAILABLE;


    public long getStatusFlagValue(){
        return 1 << this.ordinal();
    } 

}

请注意,现在您应该避免重新排序、插入(除了末尾之外)或删除条目,否则标志值会改变,数据库内容的含义也会改变。

【讨论】:

  • 有没有办法在没有丑陋的方法的情况下对此进行布尔逻辑:StatusFlag.DOCUMENT_STATUS_NOT_DEFINED.getStatusFlagValue() | StatusFlag.DOCUMENT_STATUS_MISSING_TID_DIR.getStatusFlagValue()
  • 使用我的方法,这可以作为StatusFlag.DOCUMENT_STATUS_NOT_DEFINED.flag | StatusFlag.DOCUMENT_STATUS_MISSING_TID_DIR.flag 完成,使用静态导入,它可以是DOCUMENT_STATUS_NOT_DEFINED.flag | DOCUMENT_STATUS_MISSING_TID_DIR.flag
  • 使用序号可能会导致代码脆弱。简单地重新组织枚举中的项目列表会破坏事情,因为他坚持到一个数据库。
  • 您的枚举还可以包含静态 combine()、remove() 和 test() 方法来创建枚举。 StatusFlag.combine(DOCUMENT_STATUS_OK, DOCUMENT_STATUS_...) 如果您愿意,也可以通过提供特定值来绕过排序问题。
  • 我的建议是始终将ordinal() 视为瞬态并且永远不要序列化它的值。非常适合仅运行时的解决方案,但如果序列化到数据库则应避免使用。
【解决方案4】:

更好的方法是将1 &lt;&lt; this.ordinal() 的结果存储在一个字段中 枚举值被构造。这样,您不必手动提供每个值,并且标志只计算一次。

公共枚举状态标志 {
  DOCUMENT_STATUS_NOT_DEFIND,
  DOCUMENT_STATUS_OK, 
  DOCUMENT_STATUS_MISSING_TID_DIR,
  DOCUMENT_STATUS_MISSING_TIF_FILE,
  DOCUMENT_STATUS_MISSING_PDF_FILE,
  DOCUMENT_STATUS_MISSING_OCR_FILE,
  DOCUMENT_STATUS_PAGE_COUNT_TIF,
  DOCUMENT_STATUS_PAGE_COUNT_PDF,
  DOCUMENT_STATUS_UNAVAILABLE;

  public final int flag;

  StatusFlag() { 
    this.flag = 1 << this.ordinal();
  } 
}
罢工> **更新:** 这是我没有太多 Java 经验时的旧答案。 我不再认为我的答案是有效的,因为这种方法将标志的值与排序或枚举值相结合,这很糟糕:如果更改顺序或删除枚举值,这将影响其他枚举值的标志,这可能会产生无法预料的后果。

这些天,我会使用问题中使用的方法(通过构造函数参数手动提供标志的值),因为它更易于维护:

public enum StatusFlag {

  DOCUMENT_STATUS_NOT_DEFINED(0),
  DOCUMENT_STATUS_OK(1), 
  DOCUMENT_STATUS_MISSING_TID_DIR(2),
  DOCUMENT_STATUS_MISSING_TIF_FILE(3),
  DOCUMENT_STATUS_MISSING_PDF_FILE(4),
  DOCUMENT_STATUS_MISSING_OCR_FILE(5),
  DOCUMENT_STATUS_PAGE_COUNT_TIF(6),
  DOCUMENT_STATUS_PAGE_COUNT_PDF(7),
  DOCUMENT_STATUS_UNAVAILABLE(8);

  public final int flag;

  StatusFlag(int id) { 
    this.flag = 1 << id;
  } 
}

【讨论】:

  • 为什么要预先计算和缓存移位这样一个微不足道的操作,而不是提供一种按需计算移位值的方法?
  • 在这种情况下实际上并不重要。我更喜欢将固定数据放在最终字段中,将派生数据放在方法中,这样我可以确定这些字段在运行时不会改变。
  • 在这种情况下,移位值既是固定的又是派生的。缓存它提供了时间与空间的权衡(除非额外空间的缓存和分配结果会抵消任何速度优势)。
【解决方案5】:

我已经为这个问题制作了一个完整的库: http://claude-martin.ch/enumbitset/

主要目标是将枚举类型集存储在位域中。但它也支持其他类型。

有了这个,你就不需要像“getStatusFlags()”这样的额外方法了。只需添加接口 EnumBitSetHelper 就可以在任何现有的枚举类型上使用它(它就像“特征”一样使用)。 然后每个枚举常量可以创建一个“EnumBitSet”,它具有 Java 的 EnumSet 和 BitSet 的所有方法。 然后,您可以使用这些枚举常量集并将它们转换为位域值。

它支持 BigInteger 和 long 等多种格式,可以轻松地将值存储到位字段中。 但请注意,这只适用于 Java 8 及更高版本。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-12-04
    • 2011-08-28
    • 2016-02-08
    • 1970-01-01
    • 1970-01-01
    • 2010-11-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多