Können Aufzählungen in Unterklassen unterteilt werden, um neue Elemente hinzuzufügen?

534

Ich möchte eine vorhandene Aufzählung nehmen und wie folgt weitere Elemente hinzufügen:

enum A {a,b,c}

enum B extends A {d}

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

Ist das in Java möglich?

Mike
quelle
12
Ein Grund dafür ist das Testen der Situation, in der ein ungültiger Aufzählungswert vorliegt, ohne einen ungültigen Aufzählungswert in die Kernquelle einzufügen.
Archimedes Trajano
Ja, ein Beispiel für "sprachliche" Reinheit. Ich denke, was für die "bookeeping" arbeitssparende Idee eines automatisch inkrementierenden Satzes von Ganzzahlen wie in C ++ erwünscht ist, damit Sie einen neuen Satz als Erweiterung des alten Satzes ab dem letzten Wert von 1+ starten können vorherige Menge, und wenn die Einträge benannt sind, erben Sie die Namen von der "gemeinsamen Teilmenge". Obwohl die Java-Enumeration einige nette Dinge enthält, fehlt ihr die einfache automatisierte, automatisch inkrementierende Ganzzahl-Deklarationshilfe, die die C ++ - Enumeration bietet.
Peterk
4
Wenn Sie Ihre Aufzählung um neue Werte erweitern, erstellen Sie tatsächlich keine Unterklasse, sondern eine Oberklasse. Sie können überall Basis-Enum-Werte anstelle von "erweiterter" Enum verwenden, aber nicht umgekehrt. Gemäß dem Liskov-Substitutionsprinzip ist Extended-Enum eine Oberklasse der Basis-Enum.
Ilya
@Ilya ... ja das ist wahr. Ich weise darauf hin, dass die Frage bestimmte reale Anwendungsfälle hat. Betrachten Sie aus Gründen der Argumentation eine Basis- Aufzählung von : PrimaryColours; es ist vernünftig zu wollen Super -Klasse dies zu Enum PrimaryAndPastelColoursdurch neue Farbnamen hinzufügen. Liskov ist immer noch der Elefant im Raum. So beginnt , warum nicht mit einer Basis Enum von: AllMyColours- Und dann einer Macht sub -Klasse aller Farben an: PrimaryAndPastelColoursund anschließend Unter -Klasse dies: PrimaryColours(die Hierarchie im Auge behalten). Java wird das aber auch nicht zulassen.
wird

Antworten:

450

Nein, das können Sie in Java nicht. Abgesehen von allem anderen dwäre es dann vermutlich eine Instanz von A(angesichts der normalen Idee von "Erweitert"), aber Benutzer, die nur davon wussten, Awürden nichts davon wissen - was den Punkt einer Aufzählung, die eine bekannte Menge von ist, zunichte macht Werte.

Wenn Sie uns mehr darüber erzählen können , wie Sie wollen verwenden diese, könnten wir möglicherweise alternative Lösungen vorschlagen.

Jon Skeet
quelle
516
Alle Aufzählungen erweitern implizit java.lang.Enum. Da Java keine Mehrfachvererbung unterstützt, kann eine Aufzählung nichts anderes erweitern.
Givanse
9
Der Grund, warum ich erweitern möchte, ist, dass ich eine Basisklasse namens zB IntEnum haben möchte, die so aussieht: stackoverflow.com/questions/1681976/enum-with-int-value-in-java/… . Dann könnten alle meine Aufzählungen es erweitern ... in diesem Fall nur von der Vererbung profitieren und somit müsste ich diesen "int-basierten Aufzählungscode" nicht häufig duplizieren. Ich bin neu in Java und komme aus C #, und ich hoffe, dass mir etwas fehlt. Meine aktuelle Meinung ist, dass Java-Enums im Vergleich zu C # ein Schmerz sind.
Tyler Collier
30
@Tyler: C # Aufzählungen sind nur Namen mit Telefonnummern zugeordnet sind , ohne die automatische Validierung oder irgendetwas . IMO-Enums sind das eine Bit von Java, das tatsächlich besser als C # ist.
Jon Skeet
21
Ich stimme @JonSkeet hier nicht zu. In meinem Anwendungsfall möchte ich die gesamte böse Logik in meiner großen Aufzählung trennen, die Logik verbergen und eine saubere Aufzählung definieren, die die andere, die verborgen ist, erweitert. Aufzählungen mit viel Logik übertreffen die Idee, saubere Variablen deklarieren zu lassen, sodass Sie nicht Hunderte von statischen Zeichenfolgenvariablen deklarieren müssen, damit eine Klasse mit 5 Aufzählungen nicht unlesbar und in Zeilen zu groß wird. Ich möchte nicht, dass die anderen Entwickler sich darum kümmern, diesen Code-Frieden für das nächste Projekt zu kopieren und einzufügen, und stattdessen das base_enum erweitern ... es macht für mich Sinn ...
mmm
43
@givanse ... stimmt Ihnen in Bezug auf die implizite Erweiterung von java.lang nicht zu. Enum ist die Ursache für die Nichtvererbung, da jede Klasse in Java implizit auch die Objektklasse erbt, jedoch eine andere Klasse erben kann, wie sie dann kommen würde in die Hierarchie als Object->A->Banstelle vonObject->A->B extends Object
Mickeymoon
317

Aufzählungen stellen eine vollständige Aufzählung möglicher Werte dar. Die (nicht hilfreiche) Antwort lautet also nein.

Als Beispiel für ein echtes Problem nehmen wir Wochentage, Wochenendtage und, die Gewerkschaft, Wochentage. Wir könnten alle Tage innerhalb von Wochentagen definieren, aber dann könnten wir keine Eigenschaften darstellen, die speziell für Wochentage oder Wochenendtage gelten.

Was wir tun könnten, ist drei Aufzählungstypen mit einer Zuordnung zwischen Wochentagen / Wochenendtagen und Wochentagen.

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 den Wochentag 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() { ... }
}
Tom Hawtin - Tackline
quelle
20
Gibt es damit kein Problem? Eine switch-Anweisung funktioniert nicht auf einer Schnittstelle, aber auf einer regulären Aufzählung. Nicht funktionieren mit Schalter tötet eines der schönsten Dinge bei Aufzählungen.
Manius
9
Ich denke, es könnte ein anderes Problem damit geben. Es gibt keine Gleichheit zwischen Weekday.MON und DayOfWeek.MON. Ist das nicht der andere große Vorteil von Aufzählungen? Ich habe keine bessere Lösung, nur um dies zu realisieren, während ich versuche, die beste Antwort zu finden. Das Fehlen der Fähigkeit, == zu verwenden, zwingt die Hand ein wenig.
Snekse
2
@Crusader ja, das ist genau der Kompromiss. Wenn Sie etwas Erweiterbares wünschen, können Sie keine festen switch-Anweisungen haben. Wenn Sie einen Satz fester bekannter Werte möchten, können Sie tautologisch nichts Erweiterbares haben.
Djechlin
3
Wenn Sie von enum zu interface wechseln, verlieren Sie auch den statischen Aufruf von values ​​(). Dies erschwert das Refactoring, insbesondere wenn Sie Ihre Aufzählung erweitern und die Schnittstelle als Abstraktionsbarriere zu einer festgelegten Aufzählung hinzufügen möchten.
Joshua Goldberg
4
Dieser Ansatz zum Ableiten einer Aufzählung von einer Schnittstelle wird von der Java 1.7-API verwendet, z. B. verwendet java.nio.file.Files.write () ein Array von OpenOption als letztes Argument. OpenOption ist eine Schnittstelle, aber wenn wir diese Funktion aufrufen, übergeben wir normalerweise eine StandardOpenOption-Enum-Konstante, die von OpenOption abgeleitet ist. Dies hat den Vorteil, dass es erweiterbar ist, hat aber auch Nachteile. Die Implementierung leidet unter der Tatsache, dass OpenOption eine Schnittstelle ist. Es erstellt ein HashSet <OpenOption> aus dem übergebenen Array, wenn es ein platz- und zeiteffizienteres EnumSet hätte erstellen können. Und es kann keinen Schalter verwenden.
Klitos Kyriacou
71

Die empfohlene Lösung hierfür ist das erweiterbare Aufzählungsmuster .

Dazu müssen Sie eine Schnittstelle erstellen und diese verwenden, in der Sie derzeit die Aufzählung verwenden. Lassen Sie dann die Aufzählung die Schnittstelle implementieren. Sie können weitere Konstanten hinzufügen, indem Sie festlegen, dass diese neue Aufzählung auch die Schnittstelle erweitert.

JodaStephen
quelle
Es lohnt sich, die Verwendung einer Factory-Methode in der Schnittstelle aufzurufen. Eine großartige Möglichkeit, gemeinsame Funktionen zwischen verwandten Enums zu teilen, da eine Erweiterung keine praktikable Lösung ist.
Tim Clemons
8
Können Sie weitere Details (Code :) zu diesem Muster angeben?
Dherik
3
Dieses Muster erlaubt es nicht, die Werte einer Aufzählung zu erweitern. Welches ist der Punkt in der gestellten Frage.
Eria
55

Unter dem Deckmantel ist Ihre ENUM nur eine reguläre Klasse, die vom Compiler generiert wird. Diese generierte Klasse wird erweitert java.lang.Enum. Der technische Grund, warum Sie die generierte Klasse nicht erweitern können, ist, dass die generierte Klasse istfinal . Die konzeptionellen Gründe für die Endgültigkeit werden in diesem Thema erörtert. Aber ich werde die Mechanik zur Diskussion hinzufügen.

Hier ist eine Testaufzählung:

public enum TEST {  
    ONE, TWO, THREE;
}

Der resultierende Code aus Javap:

public final class TEST extends java.lang.Enum<TEST> {
  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önnen Sie diese Klasse selbst eingeben und das "Finale" löschen. Der Compiler verhindert jedoch, dass Sie "java.lang.Enum" direkt erweitern können. Sie könnten sich entscheiden, java.lang.Enum NICHT zu erweitern, aber dann wären Ihre Klasse und ihre abgeleiteten Klassen keine Instanz von java.lang.Enum ... was für Sie in keiner Weise wirklich wichtig sein könnte!

ChrisCantrell
quelle
1
Was macht der leere statische Block? 'static {};'
Soote
1
Es enthält keinen Code. Das Programm "javap" zeigt den leeren Block.
ChrisCantrell
Seltsam, es dort zu haben, wenn es nichts tut, nicht wahr?
Soote
4
Sie haben Recht! Mein Fehler. Es ist KEIN leerer Codeblock. Wenn Sie "javap -c" ausführen, wird der tatsächliche Code im statischen Block angezeigt. Der statische Block erstellt alle ENUM-Instanzen (hier ONE, TWO und THREE). Das tut mir leid.
ChrisCantrell
1
Vielen Dank, dass Sie die klare Tatsache angegeben haben: weil java.lang.Enum für endgültig erklärt wird.
Benjamin
26
enum A {a,b,c}
enum B extends A {d}
/*B is {a,b,c,d}*/

kann geschrieben werden als:

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: Nehmen wir an, wir wollen etwas wie: Wir haben Ereignisse und verwenden Aufzählungen. Diese Aufzählungen können durch ähnliche Verarbeitung gruppiert werden. Wenn wir eine Operation mit vielen Elementen haben, starten einige Ereignisse die Operation, andere sind nur Schritte und andere beenden die Operation. Um eine solche Operation zu erfassen und lange Schalterfälle 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;
}
// Each group must implement this interface
private interface StatusGroupInterface{
    EnumSet<AtmOperationStatus> getMembers();
    void addMember(AtmOperationStatus pE);
}
// DEFINING GROUPS
public enum State_StatusGroup implements StatusGroupInterface{
    START, STEP, FAIL, FINISH;

    private List<AtmOperationStatus> members = new LinkedList<AtmOperationStatus>();

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

    @Override
    public void addMember(AtmOperationStatus pE) {
        members.add(pE);
    }
    static { // forcing initiation of dependent enum
        try {
            Class.forName(AtmOperationStatus.class.getName()); 
        } catch (ClassNotFoundException ex) { 
            throw new RuntimeException("Class AtmEventType not found", ex); 
        }
    }
}
}
//Some use of upper code:
if (p.getStatus().is(AtmOperationStatus.State_StatusGroup.FINISH)) {
    //do something
}else if (p.getStatus().is(AtmOperationStatus.State_StatusGroup.START)) {
    //do something      
}  

Fügen Sie einige erweiterte 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),
(...)

Wenn wir oben einen Fehler haben (myEvent.is (State_StatusGroup.FAIL)) und dann durch frühere Ereignisse iterieren, können wir leicht überprüfen, ob wir den Geldtransfer zurücksetzen müssen, indem wir:

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

Es kann nützlich sein für:

  1. einschließlich expliziter Metadaten zur Verarbeitungslogik, weniger zu merken
  2. Implementierung einiger Mehrfachvererbungen
  3. Wir wollen keine Klassenstrukturen verwenden, z. zum Senden von kurzen Statusmeldungen
Waldemar Wosiński
quelle
13

Hier ist ein Weg, wie ich herausgefunden habe, wie man eine Aufzählung in eine andere Aufzählung erweitert, ist ein sehr direkter Ansatz:

Angenommen, Sie haben eine Aufzählung mit gemeinsamen Konstanten:

public interface ICommonInterface {

    String getName();

}


public enum CommonEnum implements ICommonInterface {
    P_EDITABLE("editable"),
    P_ACTIVE("active"),
    P_ID("id");

    private final String name;

    EnumCriteriaComun(String name) {
        name= name;
    }

    @Override
    public String getName() {
        return this.name;
    }
}

Dann können Sie versuchen, ein Handbuch zu erstellen, das folgendermaßen erweitert wird:

public enum SubEnum implements ICommonInterface {
    P_EDITABLE(CommonEnum.P_EDITABLE ),
    P_ACTIVE(CommonEnum.P_ACTIVE),
    P_ID(CommonEnum.P_ID),
    P_NEW_CONSTANT("new_constant");

    private final String name;

    EnumCriteriaComun(CommonEnum commonEnum) {
        name= commonEnum.name;
    }

    EnumCriteriaComun(String name) {
        name= name;
    }

    @Override
    public String getName() {
        return this.name;
    }
}

Natürlich müssen Sie jedes Mal, wenn Sie eine Konstante erweitern müssen, Ihre SubEnum-Dateien ändern.

Juan Pablo G.
quelle
Interessanterweise könnten wir auch die Aufzählung toString () verwenden und am Ende Strings vergleichen. und um switch zu verwenden, müssten wir das Objekt nur in eine bekannte Aufzählung umwandeln; Das einzige Problem wären 2 Entwickler, die eine identische Enum-ID erweitern und erstellen und später versuchen, beide Codes zusammenzuführen :) Jetzt verstehe ich, warum Enum nicht erweiterbar bleiben sollte.
Wassermann Power
11

Falls Sie es verpasst haben, gibt es ein Kapitel in dem ausgezeichneten Buch " Java Effective, 2nd Edition " von Joshua Bloch .

  • Kapitel 6 - Aufzählungen und Anmerkungen
    • Punkt 34: Emulieren erweiterbarer Enums mit Schnittstellen

Extrahieren Sie hier .

Nur die Schlussfolgerung:

Ein kleiner Nachteil der Verwendung von Schnittstellen zum Emulieren erweiterbarer Aufzählungen besteht darin, dass Implementierungen nicht von einem Aufzählungstyp an einen anderen vererbt werden können. In unserem Operationsbeispiel wird die Logik zum Speichern und Abrufen des mit einer Operation verknüpften Symbols in BasicOperation und ExtendedOperation dupliziert. In diesem Fall spielt es keine Rolle, da nur sehr wenig Code dupliziert wird. Wenn mehr gemeinsam genutzte Funktionen vorhanden sind, können Sie diese in eine Hilfsklasse oder eine statische Hilfsmethode kapseln, um die Codeduplizierung zu vermeiden.

Zusammenfassend lässt sich sagen, dass Sie einen erweiterbaren Aufzählungstyp zwar nicht schreiben können, ihn jedoch emulieren können, indem Sie eine Schnittstelle schreiben, die zu einem grundlegenden Aufzählungstyp passt, der die Schnittstelle implementiert. Auf diese Weise können Clients ihre eigenen Aufzählungen schreiben, die die Schnittstelle implementieren. Diese Aufzählungen können dann überall dort verwendet werden, wo der grundlegende Aufzählungstyp verwendet werden kann, vorausgesetzt, APIs werden in Bezug auf die Schnittstelle geschrieben.

Guillaume Husta
quelle
6

Ich vermeide Aufzählungen, weil sie nicht erweiterbar sind. Um beim Beispiel des OP zu bleiben: Wenn sich A in einer Bibliothek und B in Ihrem eigenen Code befindet, können Sie A nicht erweitern, wenn es sich um eine Aufzählung handelt. So ersetze ich manchmal Aufzählungen:

// access like enum: A.a
public class A {
    public static final A a = new A();
    public static final A b = new A();
    public static final A c = new A();
/*
 * In case you need to identify your constant
 * in different JVMs, you need an id. This is the case if
 * your object is transfered between
 * different JVM instances (eg. save/load, or network).
 * Also, switch statements don't work with
 * Objects, but work with int.
 */
    public static int maxId=0;
    public int id = maxId++;
    public int getId() { return id; }
}

public class B extends A {
/*
 * good: you can do like
 * A x = getYourEnumFromSomeWhere();
 * if(x instanceof B) ...;
 * to identify which enum x
 * is of.
 */
    public static final A d = new A();
}

public class C extends A {
/* Good: e.getId() != d.getId()
 * Bad: in different JVMs, C and B
 * might be initialized in different order,
 * resulting in different IDs.
 * Workaround: use a fixed int, or hash code.
 */
    public static final A e = new A();
    public int getId() { return -32489132; };
}

Es gibt einige Gruben zu vermeiden, siehe die Kommentare im Code. Abhängig von Ihren Anforderungen ist dies eine solide, erweiterbare Alternative zu Aufzählungen.

Sulai
quelle
1
Es kann in Ordnung sein, wenn Sie nur eine Ordnungszahl für Instanzen benötigen. Aufzählungen haben aber auch eine Namenseigenschaft, die ziemlich nützlich ist.
oder
6

Auf diese Weise verbessere ich das Enum-Vererbungsmuster mit der Laufzeitprüfung im statischen Initialisierer. Die BaseKind#checkEnumExtenderÜberprüfung, dass die "Enumeration" enum alle Werte der Basis-Enum auf genau dieselbe Weise deklariert #name()und #ordinal()vollständig kompatibel bleibt.

Das Deklarieren von Werten ist immer noch kopiert und eingefügt, aber das Programm schlägt schnell fehl, wenn jemand einen Wert in der Basisklasse hinzugefügt oder geändert hat, ohne die erweiterten zu aktualisieren.

Gemeinsames Verhalten für verschiedene Aufzählungen, die sich gegenseitig erweitern:

public interface Kind {
  /**
   * Let's say we want some additional member.
   */
  String description() ;

  /**
   * Standard {@code Enum} method.
   */
  String name() ;

  /**
   * Standard {@code Enum} method.
   */
  int ordinal() ;
}

Basisaufzählung mit Überprüfungsmethode:

public enum BaseKind implements Kind {

  FIRST( "First" ),
  SECOND( "Second" ),

  ;

  private final String description ;

  public String description() {
    return description ;
  }

  private BaseKind( final String description ) {
    this.description = description ;
  }

  public static void checkEnumExtender(
      final Kind[] baseValues,
      final Kind[] extendingValues
  ) {
    if( extendingValues.length < baseValues.length ) {
      throw new IncorrectExtensionError( "Only " + extendingValues.length + " values against "
          + baseValues.length + " base values" ) ;
    }
    for( int i = 0 ; i < baseValues.length ; i ++ ) {
      final Kind baseValue = baseValues[ i ] ;
      final Kind extendingValue = extendingValues[ i ] ;
      if( baseValue.ordinal() != extendingValue.ordinal() ) {
        throw new IncorrectExtensionError( "Base ordinal " + baseValue.ordinal()
            + " doesn't match with " + extendingValue.ordinal() ) ;
      }
      if( ! baseValue.name().equals( extendingValue.name() ) ) {
        throw new IncorrectExtensionError( "Base name[ " + i + "] " + baseValue.name()
            + " doesn't match with " + extendingValue.name() ) ;
      }
      if( ! baseValue.description().equals( extendingValue.description() ) ) {
        throw new IncorrectExtensionError( "Description[ " + i + "] " + baseValue.description()
            + " doesn't match with " + extendingValue.description() ) ;
      }
    }
  }


  public static class IncorrectExtensionError extends Error {
    public IncorrectExtensionError( final String s ) {
      super( s ) ;
    }
  }

}

Erweiterungsbeispiel:

public enum ExtendingKind implements Kind {
  FIRST( BaseKind.FIRST ),
  SECOND( BaseKind.SECOND ),
  THIRD( "Third" ),
  ;

  private final String description ;

  public String description() {
    return description ;
  }

  ExtendingKind( final BaseKind baseKind ) {
    this.description = baseKind.description() ;
  }

  ExtendingKind( final String description ) {
    this.description = description ;
  }

}
Laurent Caillette
quelle
4

Basierend auf der Antwort von @Tom Hawtin - Tackline fügen wir Switch-Unterstützung hinzu.

interface Day<T> {
    ...
  T valueOf();
}

public enum Weekday implements Day<Weekday> {
    MON, TUE, WED, THU, FRI;
   Weekday valueOf(){
     return valueOf(name());
   }
}

public enum WeekendDay implements Day<WeekendDay> {
    SAT, SUN;
   WeekendDay valueOf(){
     return valueOf(name());
   }
}

Day<Weekday> wds = Weekday.MON;
Day<WeekendDay> wends = WeekendDay.SUN;

switch(wds.valueOf()){
    case MON:
    case TUE:
    case WED:
    case THU:
    case FRI:
}

switch(wends.valueOf()){
    case SAT:
    case SUN:
}
Khaled Lela
quelle
Was nützt die valueOf()Methode?
Axel Advento
@AxelAdvento Die Idee hier ist, dass wir von der Schnittstelle abhängen Day, die valueOf()dann eine Methode hat switch(Day.valueOf()), die durch WeekDay, WeekEndDayAufzählungen implementiert wird .
Khaled Lela
3

Ich schlage vor, Sie gehen umgekehrt vor.

Anstatt die vorhandene Aufzählung zu erweitern, erstellen Sie eine größere und erstellen Sie eine Teilmenge davon. Wenn Sie beispielsweise eine Aufzählung namens PET hatten und diese auf ANIMAL erweitern wollten, sollten Sie stattdessen Folgendes tun:

public enum ANIMAL {
    WOLF,CAT, DOG
} 
EnumSet<ANIMAL> pets = EnumSet.of(ANIMAL.CAT, ANIMAL.DOG);

Seien Sie vorsichtig, Haustiere sind keine unveränderlichen Sammlungen. Vielleicht möchten Sie Guava oder Java9 für mehr Sicherheit verwenden.

Guillaume Robbe
quelle
2

Nachdem ich selbst das gleiche Problem hatte, möchte ich meine Perspektive veröffentlichen. Ich denke, dass es ein paar motivierende Faktoren gibt, um so etwas zu tun:

  • Sie möchten einige verwandte Aufzählungscodes haben, jedoch in verschiedenen Klassen. In meinem Fall hatte ich eine Basisklasse mit mehreren Codes, die in einer zugeordneten Aufzählung definiert waren. Zu einem späteren Zeitpunkt (heute!) Wollte ich der Basisklasse einige neue Funktionen bereitstellen, was auch neue Codes für die Aufzählung bedeutete.
  • Die abgeleitete Klasse würde sowohl die Aufzählung der Basisklassen als auch ihre eigene unterstützen. Keine doppelten Aufzählungswerte! Also: Wie erstelle ich eine Aufzählung für die Unterklasse, die die Aufzählung des übergeordneten Elements zusammen mit den neuen Werten enthält?

Die Verwendung einer Schnittstelle ist nicht wirklich hilfreich: Sie können versehentlich doppelte Aufzählungswerte erhalten. Nicht wünschenswert.

Am Ende habe ich nur die Aufzählungen kombiniert: Dies stellt sicher, dass es keine doppelten Werte geben kann, auf Kosten einer geringeren Bindung an die zugehörige Klasse. Aber ich dachte, das doppelte Problem sei mein Hauptanliegen ...

dsummersl
quelle
2

Um zu verstehen, warum das Erweitern einer Aufzählung auf der Ebene der Sprachimplementierung nicht sinnvoll ist, sollten Sie überlegen, was passieren würde, wenn Sie eine Instanz der erweiterten Aufzählung an eine Routine übergeben, die nur die Basisaufzählung versteht. Ein vom Compiler versprochener Wechsel, bei dem alle Fälle abgedeckt waren, würde diese erweiterten Enum-Werte tatsächlich nicht abdecken.

Dies unterstreicht weiter, dass Java Enum-Werte keine Ganzzahlen sind, wie z. B. Cs: Um eine Java Enum als Array-Index zu verwenden, müssen Sie explizit nach ihrem ordinal () -Mitglied fragen, um einer Java Enum einen beliebigen Ganzzahlwert zu geben, den Sie hinzufügen müssen ein explizites Feld dafür und verweisen auf das benannte Mitglied.

Dies ist kein Kommentar zum Wunsch des OP, nur warum Java es niemals tun wird.

user2543191
quelle
1

In der Hoffnung, dass diese elegante Lösung eines Kollegen von mir sogar in diesem langen Beitrag zu sehen ist, möchte ich diesen Ansatz für Unterklassen teilen, der dem Schnittstellenansatz und darüber hinaus folgt.

Bitte beachten Sie, dass wir hier benutzerdefinierte Ausnahmen verwenden und dieser Code nur kompiliert wird, wenn Sie ihn durch Ihre Ausnahmen ersetzen.

Die Dokumentation ist umfangreich und ich hoffe, dass sie für die meisten von Ihnen verständlich ist.

Die Schnittstelle, die jede untergeordnete Aufzählung implementieren muss.

public interface Parameter {
  /**
   * Retrieve the parameters name.
   *
   * @return the name of the parameter
   */
  String getName();

  /**
   * Retrieve the parameters type.
   *
   * @return the {@link Class} according to the type of the parameter
   */
  Class<?> getType();

  /**
   * Matches the given string with this parameters value pattern (if applicable). This helps to find
   * out if the given string is a syntactically valid candidate for this parameters value.
   *
   * @param valueStr <i>optional</i> - the string to check for
   * @return <code>true</code> in case this parameter has no pattern defined or the given string
   *         matches the defined one, <code>false</code> in case <code>valueStr</code> is
   *         <code>null</code> or an existing pattern is not matched
   */
  boolean match(final String valueStr);

  /**
   * This method works as {@link #match(String)} but throws an exception if not matched.
   *
   * @param valueStr <i>optional</i> - the string to check for
   * @throws ArgumentException with code
   *           <dl>
   *           <dt>PARAM_MISSED</dt>
   *           <dd>if <code>valueStr</code> is <code>null</code></dd>
   *           <dt>PARAM_BAD</dt>
   *           <dd>if pattern is not matched</dd>
   *           </dl>
   */
  void matchEx(final String valueStr) throws ArgumentException;

  /**
   * Parses a value for this parameter from the given string. This method honors the parameters data
   * type and potentially other criteria defining a valid value (e.g. a pattern).
   *
   * @param valueStr <i>optional</i> - the string to parse the parameter value from
   * @return the parameter value according to the parameters type (see {@link #getType()}) or
   *         <code>null</code> in case <code>valueStr</code> was <code>null</code>.
   * @throws ArgumentException in case <code>valueStr</code> is not parsable as a value for this
   *           parameter.
   */
  Object parse(final String valueStr) throws ArgumentException;

  /**
   * Converts the given value to its external form as it is accepted by {@link #parse(String)}. For
   * most (ordinary) parameters this is simply a call to {@link String#valueOf(Object)}. In case the
   * parameter types {@link Object#toString()} method does not return the external form (e.g. for
   * enumerations), this method has to be implemented accordingly.
   *
   * @param value <i>mandatory</i> - the parameters value
   * @return the external form of the parameters value, never <code>null</code>
   * @throws InternalServiceException in case the given <code>value</code> does not match
   *           {@link #getType()}
   */
  String toString(final Object value) throws InternalServiceException;
}

Die implementierende ENUM-Basisklasse.

public enum Parameters implements Parameter {
  /**
   * ANY ENUM VALUE
   */
  VALUE(new ParameterImpl<String>("VALUE", String.class, "[A-Za-z]{3,10}"));

  /**
   * The parameter wrapped by this enum constant.
   */
  private Parameter param;

  /**
   * Constructor.
   *
   * @param param <i>mandatory</i> - the value for {@link #param}
   */
  private Parameters(final Parameter param) {
    this.param = param;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String getName() {
    return this.param.getName();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Class<?> getType() {
    return this.param.getType();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean match(final String valueStr) {
    return this.param.match(valueStr);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void matchEx(final String valueStr) {
    this.param.matchEx(valueStr);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Object parse(final String valueStr) throws ArgumentException {
    return this.param.parse(valueStr);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String toString(final Object value) throws InternalServiceException {
    return this.param.toString(value);
  }
}

Die untergeordnete ENUM, die von der Basisklasse "erbt".

public enum ExtendedParameters implements Parameter {
  /**
   * ANY ENUM VALUE
   */
  VALUE(my.package.name.VALUE);

  /**
   * EXTENDED ENUM VALUE
   */
  EXTENDED_VALUE(new ParameterImpl<String>("EXTENDED_VALUE", String.class, "[0-9A-Za-z_.-]{1,20}"));

  /**
   * The parameter wrapped by this enum constant.
   */
  private Parameter param;

  /**
   * Constructor.
   *
   * @param param <i>mandatory</i> - the value for {@link #param}
   */
  private Parameters(final Parameter param) {
    this.param = param;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String getName() {
    return this.param.getName();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Class<?> getType() {
    return this.param.getType();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean match(final String valueStr) {
    return this.param.match(valueStr);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void matchEx(final String valueStr) {
    this.param.matchEx(valueStr);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Object parse(final String valueStr) throws ArgumentException {
    return this.param.parse(valueStr);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String toString(final Object value) throws InternalServiceException {
    return this.param.toString(value);
  }
}

Zum Schluss das generische ParameterImpl, um einige Dienstprogramme hinzuzufügen.

public class ParameterImpl<T> implements Parameter {
  /**
   * The default pattern for numeric (integer, long) parameters.
   */
  private static final Pattern NUMBER_PATTERN = Pattern.compile("[0-9]+");

  /**
   * The default pattern for parameters of type boolean.
   */
  private static final Pattern BOOLEAN_PATTERN = Pattern.compile("0|1|true|false");

  /**
   * The name of the parameter, never <code>null</code>.
   */
  private final String name;

  /**
   * The data type of the parameter.
   */
  private final Class<T> type;

  /**
   * The validation pattern for the parameters values. This may be <code>null</code>.
   */
  private final Pattern validator;

  /**
   * Shortcut constructor without <code>validatorPattern</code>.
   *
   * @param name <i>mandatory</i> - the value for {@link #name}
   * @param type <i>mandatory</i> - the value for {@link #type}
   */
  public ParameterImpl(final String name, final Class<T> type) {
    this(name, type, null);
  }

  /**
   * Constructor.
   *
   * @param name <i>mandatory</i> - the value for {@link #name}
   * @param type <i>mandatory</i> - the value for {@link #type}
   * @param validatorPattern - <i>optional</i> - the pattern for {@link #validator}
   *          <dl>
   *          <dt style="margin-top:0.25cm;"><i>Note:</i>
   *          <dd>The default validation patterns {@link #NUMBER_PATTERN} or
   *          {@link #BOOLEAN_PATTERN} are applied accordingly.
   *          </dl>
   */
  public ParameterImpl(final String name, final Class<T> type, final String validatorPattern) {
    this.name = name;
    this.type = type;
    if (null != validatorPattern) {
      this.validator = Pattern.compile(validatorPattern);

    } else if (Integer.class == this.type || Long.class == this.type) {
      this.validator = NUMBER_PATTERN;
    } else if (Boolean.class == this.type) {
      this.validator = BOOLEAN_PATTERN;
    } else {
      this.validator = null;
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean match(final String valueStr) {
    if (null == valueStr) {
      return false;
    }
    if (null != this.validator) {
      final Matcher matcher = this.validator.matcher(valueStr);
      return matcher.matches();
    }
    return true;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void matchEx(final String valueStr) throws ArgumentException {
    if (false == this.match(valueStr)) {
      if (null == valueStr) {
        throw ArgumentException.createEx(ErrorCode.PARAM_MISSED, "The value must not be null",
            this.name);
      }
      throw ArgumentException.createEx(ErrorCode.PARAM_BAD, "The value must match the pattern: "
          + this.validator.pattern(), this.name);
    }
  }

  /**
   * Parse the parameters value from the given string value according to {@link #type}. Additional
   * the value is checked by {@link #matchEx(String)}.
   *
   * @param valueStr <i>optional</i> - the string value to parse the value from
   * @return the parsed value, may be <code>null</code>
   * @throws ArgumentException in case the parameter:
   *           <ul>
   *           <li>does not {@link #matchEx(String)} the {@link #validator}</li>
   *           <li>cannot be parsed according to {@link #type}</li>
   *           </ul>
   * @throws InternalServiceException in case the type {@link #type} cannot be handled. This is a
   *           programming error.
   */
  @Override
  public T parse(final String valueStr) throws ArgumentException, InternalServiceException {
    if (null == valueStr) {
      return null;
    }
    this.matchEx(valueStr);

    if (String.class == this.type) {
      return this.type.cast(valueStr);
    }
    if (Boolean.class == this.type) {
      return this.type.cast(Boolean.valueOf(("1".equals(valueStr)) || Boolean.valueOf(valueStr)));
    }
    try {
      if (Integer.class == this.type) {
        return this.type.cast(Integer.valueOf(valueStr));
      }
      if (Long.class == this.type) {
        return this.type.cast(Long.valueOf(valueStr));
      }
    } catch (final NumberFormatException e) {
      throw ArgumentException.createEx(ErrorCode.PARAM_BAD, "The value cannot be parsed as "
          + this.type.getSimpleName().toLowerCase() + ".", this.name);
    }

    return this.parseOther(valueStr);
  }

  /**
   * Field access for {@link #name}.
   *
   * @return the value of {@link #name}.
   */
  @Override
  public String getName() {
    return this.name;
  }

  /**
   * Field access for {@link #type}.
   *
   * @return the value of {@link #type}.
   */
  @Override
  public Class<T> getType() {
    return this.type;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final String toString(final Object value) throws InternalServiceException {
    if (false == this.type.isAssignableFrom(value.getClass())) {
      throw new InternalServiceException(ErrorCode.PANIC,
          "Parameter.toString(): Bad type of value. Expected {0} but is {1}.", this.type.getName(),
          value.getClass().getName());
    }
    if (String.class == this.type || Integer.class == this.type || Long.class == this.type) {
      return String.valueOf(value);
    }
    if (Boolean.class == this.type) {
      return Boolean.TRUE.equals(value) ? "1" : "0";
    }

    return this.toStringOther(value);
  }

  /**
   * Parse parameter values of other (non standard types). This method is called by
   * {@link #parse(String)} in case {@link #type} is none of the supported standard types (currently
   * String, Boolean, Integer and Long). It is intended for extensions.
   * <dl>
   * <dt style="margin-top:0.25cm;"><i>Note:</i>
   * <dd>This default implementation always throws an InternalServiceException.
   * </dl>
   *
   * @param valueStr <i>mandatory</i> - the string value to parse the value from
   * @return the parsed value, may be <code>null</code>
   * @throws ArgumentException in case the parameter cannot be parsed according to {@link #type}
   * @throws InternalServiceException in case the type {@link #type} cannot be handled. This is a
   *           programming error.
   */
  protected T parseOther(final String valueStr) throws ArgumentException, InternalServiceException {
    throw new InternalServiceException(ErrorCode.PANIC,
        "ParameterImpl.parseOther(): Unsupported parameter type: " + this.type.getName());
  }

  /**
   * Convert the values of other (non standard types) to their external form. This method is called
   * by {@link #toString(Object)} in case {@link #type} is none of the supported standard types
   * (currently String, Boolean, Integer and Long). It is intended for extensions.
   * <dl>
   * <dt style="margin-top:0.25cm;"><i>Note:</i>
   * <dd>This default implementation always throws an InternalServiceException.
   * </dl>
   *
   * @param value <i>mandatory</i> - the parameters value
   * @return the external form of the parameters value, never <code>null</code>
   * @throws InternalServiceException in case the given <code>value</code> does not match
   *           {@link #getClass()}
   */
  protected String toStringOther(final Object value) throws InternalServiceException {
    throw new InternalServiceException(ErrorCode.PANIC,
        "ParameterImpl.toStringOther(): Unsupported parameter type: " + this.type.getName());
  }
}
Dr4gon
quelle
0

Mein Weg zum Code wäre wie folgt:

// enum A { a, b, c }
static final Set<Short> enumA = new LinkedHashSet<>(Arrays.asList(new Short[]{'a','b','c'}));

// enum B extends A { d }
static final Set<Short> enumB = new LinkedHashSet<>(enumA);
static {
    enumB.add((short) 'd');
    // If you have to add more elements:
    // enumB.addAll(Arrays.asList(new Short[]{ 'e', 'f', 'g', '♯', '♭' }));
}

LinkedHashSetsieht vor, dass jeder Eintrag nur einmal existiert und dass ihre Reihenfolge erhalten bleibt. Wenn die Reihenfolge keine Rolle spielt, können Sie HashSetstattdessen verwenden. Der folgende Code ist in Java nicht möglich :

for (A a : B.values()) { // enum B extends A { d }
    switch (a) {
        case a:
        case b:
        case c:
            System.out.println("Value is: " + a.toString());
        break;
        default:
            throw new IllegalStateException("This should never happen.");
    }
}

Der Code kann wie folgt geschrieben werden:

for (Short a : enumB) {
    switch (a) {
        case 'a':
        case 'b':
        case 'c':
            System.out.println("Value is: " + new String(Character.toChars(a)));
        break;
        default:
            throw new IllegalStateException("This should never happen.");
    }
}

Ab Java 7 können Sie dasselbe sogar tun mit String:

// enum A { BACKWARDS, FOREWARDS, STANDING }
static final Set<String> enumA = new LinkedHashSet<>(Arrays.asList(new String[] {
        "BACKWARDS", "FOREWARDS", "STANDING" }));

// enum B extends A { JUMP }
static final Set<String> enumB = new LinkedHashSet<>(enumA);
static {
    enumB.add("JUMP");
}

Verwenden des Enum-Ersatzes:

for (String a : enumB) {
    switch (a) {
        case "BACKWARDS":
        case "FOREWARDS":
        case "STANDING":
            System.out.println("Value is: " + a);
        break;
        default:
            throw new IllegalStateException("This should never happen.");
    }
}
Matthias Ronge
quelle