607 Stimmen

Können Enums untergeordnet werden, um neue Elemente hinzuzufügen?

Ich möchte ein bereits vorhandenes enum nehmen und weitere Elemente wie folgt hinzufügen:

enum A {a,b,c}

enum B erweitert A {d}

/*B ist {a,b,c,d}*/

Ist dies in Java möglich?

15 Stimmen

Ein Grund dafür ist, dies zu tun, ist das Testen der Situation, in der ein ungültiger Enum-Wert vorhanden ist, ohne einen ungültigen Enum-Wert im Kernquellcode einzuführen.

0 Stimmen

Ja, ein Beispiel für "linguistische" Reinheit. Ich denke, was gewünscht wird, ist die "buchhalterische" Arbeitserleichterungsidee eines automatisch inkrementierenden Satzes von Ganzzahlen wie man es in C++ hat, damit man einen neuen Satz als Erweiterung des alten Satzes beginnen kann, beginnend bei 1 + dem letzten Wert des vorherigen Satzes, und wenn die Einträge benannt sind, die Namen aus dem "gemeinsamen Subset" erben. Obwohl das Java Enum einige schöne Dinge an sich hat, fehlt ihm die einfache automatisierte Hilfe zur Deklaration von Auto-Inkrementierungsintegern, die C++ Enum bereitstellt.

5 Stimmen

Tatsächlich, wenn Sie Ihren Aufzählungstyp mit neuen Werten erweitern, erstellen Sie keine Unterklasse, sondern eine Oberklasse. Sie können überall die Basisenumwerte anstelle des „erweiterten“ Enums verwenden, jedoch nicht umgekehrt. Daher ist gemäß dem Liskovschen Substitutionsprinzip der erweiterte Aufzählungstyp die Oberklasse des Basisenums.

499voto

Jon Skeet Punkte 1325502

Nein, du kannst das nicht in Java machen. Abgesehen davon wäre d dann vermutlich eine Instanz von A (bei der normalen Idee von "extends"), aber Benutzer, die nur über A Bescheid wüssten, würden nichts darüber wissen - was den Sinn eines Enums als bekannte Wertemenge zunichte macht.

Wenn du uns mehr darüber erzählen könntest, wie du das nutzen möchtest, könnten wir potenziell alternative Lösungen vorschlagen.

338voto

Tom Hawtin - tackline Punkte 142461

Enums repräsentieren eine vollständige Aufzählung möglicher Werte. Die (unhilfreiche) Antwort ist also nein.

Als Beispiel für ein echtes Problem nehmen wir Werktage, Wochenendtage und die Vereinigung, Tage der Woche. Wir könnten alle Tage innerhalb von Tagen-der-Woche definieren, könnten aber dann Eigenschaften, die speziell für Werktage und Wochenendtage sind, nicht darstellen.

Was wir tun könnten, ist drei Enum-Typen mit einer Zuordnung zwischen Werktagen/Wochenendtagen und Tagen-der-Woche zu haben.

public enum Weekday {
    MON, TUE, WED, THU, FRI;
    public DayOfWeek toDayOfWeek() { ... }
}
public enum WeekendDay {
    SAT, SUN;
    public DayOfWeek toDayOfWeek() { ... }
}
public enum DayOfWeek {
    MON, TUE, WED, THU, FRI, SAT, SUN;
}

Alternativ könnten wir eine offene Schnittstelle für Tag-der-Woche haben:

interface Day {
    ...
}
public enum Weekday implements Day {
    MON, TUE, WED, THU, FRI;
}
public enum WeekendDay implements Day {
    SAT, SUN;
}

Oder wir könnten die beiden Ansätze kombinieren:

interface Day {
    ...
}
public enum Weekday implements Day {
    MON, TUE, WED, THU, FRI;
    public DayOfWeek toDayOfWeek() { ... }
}
public enum WeekendDay implements Day {
    SAT, SUN;
    public DayOfWeek toDayOfWeek() { ... }
}
public enum DayOfWeek {
    MON, TUE, WED, THU, FRI, SAT, SUN;
    public Day toDay() { ... }
}

82voto

JodaStephen Punkte 57302

Die empfohlene Lösung dafür ist das erweiterbare Enum-Muster.

Dies beinhaltet das Erstellen einer Schnittstelle und deren Verwendung anstelle des Enums. Dann soll das Enum die Schnittstelle implementieren. Sie können weitere Konstanten hinzufügen, indem Sie ein zusätzliches Enum/Klasse hinzufügen, das auch die Schnittstelle erweitert. Hier ist die allgemeine Idee:

public interface TrafficLights {
  public abstract String getColour();
}
public enum StandardTrafficLights implements TrafficLights {
  RED, YELLOW, GREEN;
  public String getColour() {
    return name();
  }
}
public enum WeirdTrafficLights implements TrafficLights {
  DOUBLE_RED;
  public String getColour() {
    return name();
  }
}

Beachten Sie, dass Sie, wenn Sie so etwas wie TrafficLights.valueof(String) möchten, dies selbst implementieren müssen.

61voto

ChrisCantrell Punkte 3663

Unter der Haube ist Ihr ENUM nur eine normale Klasse, die vom Compiler generiert wird. Diese generierte Klasse erweitert java.lang.Enum. Der technische Grund, warum Sie die generierte Klasse nicht erweitern können, ist, dass die generierte Klasse final ist. Die konzeptionellen Gründe dafür, dass sie final ist, werden in diesem Thema diskutiert. Aber ich werde die Mechanik zur Diskussion hinzufügen.

Hier ist ein Test-Enum:

public enum TEST {
    ONE, TWO, THREE;
}

Der resultierende Code von javap:

public final class TEST extends java.lang.Enum {
  public static final TEST ONE;
  public static final TEST TWO;
  public static final TEST THREE;
  static {};
  public static TEST[] values();
  public static TEST valueOf(java.lang.String);
}

Möglicherweise könnten Sie diese Klasse selbst schreiben und das "final" weglassen. Aber der Compiler verhindert, dass Sie "java.lang.Enum" direkt erweitern. Sie könnten sich entscheiden, java.lang.Enum NICHT zu erweitern, aber dann wäre Ihre Klasse und ihre abgeleiteten Klassen keine Instanz von java.lang.Enum ... was für Sie möglicherweise ohnehin nicht wichtig ist!

31voto

Waldemar Wosiński Punkte 1417
public enum All {
    a       (ClassGroup.A,ClassGroup.B),
    b       (ClassGroup.A,ClassGroup.B),
    c       (ClassGroup.A,ClassGroup.B),
    d       (ClassGroup.B) 
...
  • ClassGroup.B.getMembers() enthält {a,b,c,d}

Wie es nützlich sein kann: Angenommen, wir wollen etwas wie: Wir haben Events und verwenden Enums. Diese Enums können nach ähnlicher Verarbeitung gruppiert werden. Wenn wir Operationen mit vielen Elementen haben, beginnen einige Events die Operation, einige sind nur Schritte und andere beenden die Operation. Um solche Operationen zu sammeln und lange switch case-Anweisungen zu vermeiden, können wir sie wie im Beispiel gruppieren und verwenden:

if(myEvent.is(State_StatusGroup.START)) makeNewOperationObject()..
if(myEnum.is(State_StatusGroup.STEP)) makeSomeSeriousChanges()..
if(myEnum.is(State_StatusGroup.FINISH)) closeTransactionOrSomething()..

Beispiel:

public enum AtmOperationStatus {
STARTED_BY_SERVER       (State_StatusGroup.START),
SUCCESS             (State_StatusGroup.FINISH),
FAIL_TOKEN_TIMEOUT      (State_StatusGroup.FAIL, 
                    State_StatusGroup.FINISH),
FAIL_NOT_COMPLETE       (State_StatusGroup.FAIL,
                    State_StatusGroup.STEP),
FAIL_UNKNOWN            (State_StatusGroup.FAIL,
                    State_StatusGroup.FINISH),
(...)

private AtmOperationStatus(StatusGroupInterface ... pList){
    for (StatusGroupInterface group : pList){
        group.addMember(this);
    }
}
public boolean is(StatusGroupInterface with){
    for (AtmOperationStatus eT : with.getMembers()){
        if( eT .equals(this))   return true;
    }
    return false;
}
// Jede Gruppe muss dieses Interface implementieren
private interface StatusGroupInterface{
    EnumSet getMembers();
    void addMember(AtmOperationStatus pE);
}
// DEFINIEREN VON GRUPPEN
public enum State_StatusGroup implements StatusGroupInterface{
    START, STEP, FAIL, FINISH;

    private List members = new LinkedList();

    @Override
    public EnumSet getMembers() {
        return EnumSet.copyOf(members);
    }

    @Override
    public void addMember(AtmOperationStatus pE) {
        members.add(pE);
    }
    static { // Erzwingen der Initialisierung des abhängigen Enums
        try {
            Class.forName(AtmOperationStatus.class.getName()); 
        } catch (ClassNotFoundException ex) { 
            throw new RuntimeException("Class AtmEventType not found", ex); 
        }
    }
}
}
// Einige Verwendungen des obigen Codes:
if (p.getStatus().is(AtmOperationStatus.State_StatusGroup.FINISH)) {
    //etwas tun
}else if (p.getStatus().is(AtmOperationStatus.State_StatusGroup.START)) {
    //etwas tun      
}  

Fügen Sie etwas Fortgeschrittenes hinzu:

public enum AtmEventType {

USER_DEPOSIT        (Status_EventsGroup.WITH_STATUS,
              Authorization_EventsGroup.USER_AUTHORIZED,
              ChangedMoneyAccountState_EventsGroup.CHANGED,
              OperationType_EventsGroup.DEPOSIT,
              ApplyTo_EventsGroup.CHANNEL),
SERVICE_DEPOSIT     (Status_EventsGroup.WITH_STATUS,
              Authorization_EventsGroup.TERMINAL_AUTHORIZATION,
              ChangedMoneyAccountState_EventsGroup.CHANGED,
              OperationType_EventsGroup.DEPOSIT,
              ApplyTo_EventsGroup.CHANNEL),
DEVICE_MALFUNCTION  (Status_EventsGroup.WITHOUT_STATUS,
              Authorization_EventsGroup.TERMINAL_AUTHORIZATION,
              ChangedMoneyAccountState_EventsGroup.DID_NOT_CHANGED,
              ApplyTo_EventsGroup.DEVICE),
CONFIGURATION_4_C_CHANGED(Status_EventsGroup.WITHOUT_STATUS,
              ApplyTo_EventsGroup.TERMINAL,
              ChangedMoneyAccountState_EventsGroup.DID_NOT_CHANGED),
(...)

Oben, wenn ein Fehler vorliegt (myEvent.is(State_StatusGroup.FAIL)), können wir beim Iterieren durch vorherige Events leicht überprüfen, ob wir den Geldtransfer rückgängig machen müssen, indem wir:

if(myEvent2.is(ChangedMoneyAccountState_EventsGroup.CHANGED)) rollBack()..

Es kann nützlich sein für:

  1. einschließen von expliziten Metadaten über Verarbeitungslogik, weniger zum Merken
  2. Implementierung von Multi-Vererbung
  3. wir wollen keine Klassenstrukturen verwenden, z.B. für das Senden von kurzen Statusmeldungen

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