Реализация битового поля с использованием java перечислений

Я поддерживаю большой архив документов, и я часто использую битовые поля для записи состояния моих документов во время обработки или при их проверке. В моем устаревшем коде просто используются статические 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);

    }

}

Ответ 1

ваш подход - это именно то, как это сделать.

Ответ 2

Вместо определения параметров конструктора вы можете просто использовать внутреннее значение 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();
    } 

}

Обратите внимание, что теперь вы должны воздерживаться от переупорядочения, вставки (кроме конца) или удаления записей, иначе значения флага будут меняться, и значение содержимого вашей базы данных изменится.

Ответ 3

Немного лучший способ - сохранить результат 1 << this.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 final int flag;

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

EDIT: может быть полезно также иметь метод isFlagSet.

Ответ 4

Не указывайте свои значения перечислений. Используйте EnumSet, чтобы объединить их, и используйте Enum.ordinal(), когда они сохраняются для преобразования в/из одного целого. Вы также можете найти Class.getEnumConstants() полезным при восстановлении набора из целого числа.

Ответ 5

Я создал полную библиотеку для этой проблемы: http://claude-martin.ch/enumbitset/

Основная цель состояла в том, чтобы хранить наборы типов перечислений в битовых полях. Но он также поддерживает другие типы.

С этим вам не нужны никакие дополнительные методы, такие как "getStatusFlags()". Его можно использовать для любого существующего типа перечисления, просто добавив интерфейс EnumBitSetHelper (он используется как "признак" ). Каждая константа перечисления может затем создать "EnumBitSet", который имеет все методы Java EnumSet и BitSet. Затем вы можете работать с этими наборами констант enum и преобразовывать их в значения битового поля.

Он поддерживает множество форматов, таких как BigInteger, и позволяет легко сохранить значение в поле бит. Но обратите внимание, что это работает только с Java версии 8 и новее.