50 Stimmen

Implementierung eines Bitfelds mit Java-Enums

Ich verwalte ein großes Dokumentenarchiv und verwende oft Bitfelder, um den Status meiner Dokumente während der Verarbeitung oder bei der Validierung aufzuzeichnen. Mein Legacy-Code verwendet einfach statische int-Konstanten wie z. B.:

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

Dies macht es ziemlich einfach, den Zustand eines Dokuments anzugeben, indem die entsprechenden Kennzeichen gesetzt werden. Zum Beispiel:

status = DOCUMENT_STATUS_NO_TIF_FILE | DOCUMENT_STATUS_NO_PDF_FILE;

Da der Ansatz, statische Konstanten zu verwenden, eine schlechte Praxis ist und ich den Code verbessern möchte, wollte ich Enums verwenden, um dasselbe zu erreichen. Es gibt einige Anforderungen, eine davon ist die Notwendigkeit, den Status in einer Datenbank als numerischen Typ zu speichern. Es besteht also die Notwendigkeit, die Aufzählungskonstanten in einen numerischen Wert umzuwandeln. Im Folgenden stelle ich meinen ersten Ansatz vor, und ich frage mich, ob dies der richtige Weg ist, um dies zu erreichen.

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);

    }

}

33voto

Paŭlo Ebermann Punkte 70779

Anstatt Konstruktorparameter zu definieren, könnten Sie einfach die interne ordinal() Wert, um dies zu berechnen.

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();
    } 

}

Bitte beachten Sie, dass Sie jetzt keine Einträge neu anordnen, einfügen (außer am Ende) oder löschen sollten, da sich sonst die Merkerwerte ändern und die Bedeutung Ihres Datenbankinhalts verändert wird.

24voto

Ray Tayek Punkte 9433

Ihr Ansatz ist genau der richtige Weg.

10voto

ChrisBlom Punkte 1254

Eine etwas bessere Möglichkeit wäre, das Ergebnis von 1 << this.ordinal() in einem Feld, wenn die Enum-Werte konstruiert werden. Auf diese Weise müssen Sie nicht jeden Wert manuell eingeben, und das Kennzeichen wird nur einmal berechnet.

~~public enum StatusFlag {

      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();
      } 
    }~~ \*\*Aktualisierung:\*\* Dies ist eine alte Antwort aus der Zeit, als ich noch nicht viel Java-Erfahrung hatte. Ich glaube nicht mehr, dass meine Antwort gültig ist, da dieser Ansatz den Wert des Flags an die Reihenfolge oder die Enum-Werte koppelt, was schlecht ist: Wenn die Reihenfolge geändert wird oder Enum-Werte entfernt werden, wirkt sich dies auf die Flags anderer Enum-Werte aus, was unvorhergesehene Folgen haben kann.

Heutzutage würde ich den in der Frage verwendeten Ansatz verwenden (den Wert des Kennzeichens manuell über einen Konstruktorparameter bereitstellen), da er besser zu pflegen ist:

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;
  } 
}

6voto

OrangeDog Punkte 33207

Geben Sie Ihren Enums keine Werte. Verwenden Sie eine EnumSet um sie zu kombinieren, und verwenden Sie Enum.ordinal() beim Persistieren, um in/aus einer einzelnen Ganzzahl zu konvertieren. Sie könnten auch finden Class.getEnumConstants() nützlich bei der Rekonstruktion der Menge aus der ganzen Zahl.

5voto

Claude Martin Punkte 704

Ich habe eine komplette Bibliothek für dieses Problem erstellt: http://claude-martin.ch/enumbitset/

Das Hauptziel war es, Mengen von Enum-Typen in Bitfeldern zu speichern. Es werden aber auch andere Typen unterstützt.

Damit würden Sie keine zusätzlichen Methoden wie "getStatusFlags()" benötigen. Es kann für jeden bestehenden Enum-Typ verwendet werden, indem man einfach die Schnittstelle EnumBitSetHelper (sie wird wie ein "Trait" verwendet). Jede Enum-Konstante kann dann ein "EnumBitSet" erstellen, das alle Methoden von Javas EnumSet und BitSet enthält. Dann können Sie mit diesen Mengen von Enum-Konstanten arbeiten und sie in Bitfeldwerte umwandeln.

Es unterstützt viele Formate wie BigInteger und long, um den Wert einfach in einem Bitfeld zu speichern. Beachten Sie jedoch, dass dies nur mit Java Version 8 und neuer funktioniert.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X