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.
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.
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.
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.
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:
publicenum TEST {
ONE, TWO, THREE;}
Der resultierende Code aus Javap:
publicfinalclass TEST extends java.lang.Enum<TEST>{publicstaticfinal TEST ONE;publicstaticfinal TEST TWO;publicstaticfinal TEST THREE;static{};publicstatic TEST[] values();publicstatic 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!
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:
publicenumAll{
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:
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:
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
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.
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.apublicclass A {publicstaticfinal A a =new A();publicstaticfinal A b =new A();publicstaticfinal 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.
*/publicstaticint maxId=0;publicint id = maxId++;publicint getId(){return id;}}publicclass B extends A {/*
* good: you can do like
* A x = getYourEnumFromSomeWhere();
* if(x instanceof B) ...;
* to identify which enum x
* is of.
*/publicstaticfinal A d =new A();}publicclass 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.
*/publicstaticfinal A e =new A();publicint 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.
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:
publicinterfaceKind{/**
* 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:
publicenumBaseKindimplementsKind{
FIRST("First"),
SECOND("Second"),;privatefinalString description ;publicString description(){return description ;}privateBaseKind(finalString description ){this.description = description ;}publicstaticvoid checkEnumExtender(finalKind[] baseValues,finalKind[] extendingValues
){if( extendingValues.length < baseValues.length ){thrownewIncorrectExtensionError("Only "+ extendingValues.length +" values against "+ baseValues.length +" base values");}for(int i =0; i < baseValues.length ; i ++){finalKind baseValue = baseValues[ i ];finalKind extendingValue = extendingValues[ i ];if( baseValue.ordinal()!= extendingValue.ordinal()){thrownewIncorrectExtensionError("Base ordinal "+ baseValue.ordinal()+" doesn't match with "+ extendingValue.ordinal());}if(! baseValue.name().equals( extendingValue.name())){thrownewIncorrectExtensionError("Base name[ "+ i +"] "+ baseValue.name()+" doesn't match with "+ extendingValue.name());}if(! baseValue.description().equals( extendingValue.description())){thrownewIncorrectExtensionError("Description[ "+ i +"] "+ baseValue.description()+" doesn't match with "+ extendingValue.description());}}}publicstaticclassIncorrectExtensionErrorextendsError{publicIncorrectExtensionError(finalString s ){super( s );}}}
@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:
publicenum 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.
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 ...
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.
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.
publicinterfaceParameter{/**
* 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(finalString 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(finalString valueStr)throwsArgumentException;/**
* 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(finalString valueStr)throwsArgumentException;/**
* 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(finalObject value)throwsInternalServiceException;}
Die implementierende ENUM-Basisklasse.
publicenumParametersimplementsParameter{/**
* ANY ENUM VALUE
*/
VALUE(newParameterImpl<String>("VALUE",String.class,"[A-Za-z]{3,10}"));/**
* The parameter wrapped by this enum constant.
*/privateParameter param;/**
* Constructor.
*
* @param param <i>mandatory</i> - the value for {@link #param}
*/privateParameters(finalParameter param){this.param = param;}/**
* {@inheritDoc}
*/@OverridepublicString getName(){returnthis.param.getName();}/**
* {@inheritDoc}
*/@OverridepublicClass<?> getType(){returnthis.param.getType();}/**
* {@inheritDoc}
*/@Overridepublicboolean match(finalString valueStr){returnthis.param.match(valueStr);}/**
* {@inheritDoc}
*/@Overridepublicvoid matchEx(finalString valueStr){this.param.matchEx(valueStr);}/**
* {@inheritDoc}
*/@OverridepublicObject parse(finalString valueStr)throwsArgumentException{returnthis.param.parse(valueStr);}/**
* {@inheritDoc}
*/@OverridepublicString toString(finalObject value)throwsInternalServiceException{returnthis.param.toString(value);}}
Die untergeordnete ENUM, die von der Basisklasse "erbt".
publicenumExtendedParametersimplementsParameter{/**
* ANY ENUM VALUE
*/
VALUE(my.package.name.VALUE);/**
* EXTENDED ENUM VALUE
*/
EXTENDED_VALUE(newParameterImpl<String>("EXTENDED_VALUE",String.class,"[0-9A-Za-z_.-]{1,20}"));/**
* The parameter wrapped by this enum constant.
*/privateParameter param;/**
* Constructor.
*
* @param param <i>mandatory</i> - the value for {@link #param}
*/privateParameters(finalParameter param){this.param = param;}/**
* {@inheritDoc}
*/@OverridepublicString getName(){returnthis.param.getName();}/**
* {@inheritDoc}
*/@OverridepublicClass<?> getType(){returnthis.param.getType();}/**
* {@inheritDoc}
*/@Overridepublicboolean match(finalString valueStr){returnthis.param.match(valueStr);}/**
* {@inheritDoc}
*/@Overridepublicvoid matchEx(finalString valueStr){this.param.matchEx(valueStr);}/**
* {@inheritDoc}
*/@OverridepublicObject parse(finalString valueStr)throwsArgumentException{returnthis.param.parse(valueStr);}/**
* {@inheritDoc}
*/@OverridepublicString toString(finalObject value)throwsInternalServiceException{returnthis.param.toString(value);}}
Zum Schluss das generische ParameterImpl, um einige Dienstprogramme hinzuzufügen.
publicclassParameterImpl<T>implementsParameter{/**
* The default pattern for numeric (integer, long) parameters.
*/privatestaticfinalPattern NUMBER_PATTERN =Pattern.compile("[0-9]+");/**
* The default pattern for parameters of type boolean.
*/privatestaticfinalPattern BOOLEAN_PATTERN =Pattern.compile("0|1|true|false");/**
* The name of the parameter, never <code>null</code>.
*/privatefinalString name;/**
* The data type of the parameter.
*/privatefinalClass<T> type;/**
* The validation pattern for the parameters values. This may be <code>null</code>.
*/privatefinalPattern 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}
*/publicParameterImpl(finalString name,finalClass<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>
*/publicParameterImpl(finalString name,finalClass<T> type,finalString validatorPattern){this.name = name;this.type = type;if(null!= validatorPattern){this.validator =Pattern.compile(validatorPattern);}elseif(Integer.class==this.type ||Long.class==this.type){this.validator = NUMBER_PATTERN;}elseif(Boolean.class==this.type){this.validator = BOOLEAN_PATTERN;}else{this.validator =null;}}/**
* {@inheritDoc}
*/@Overridepublicboolean match(finalString valueStr){if(null== valueStr){returnfalse;}if(null!=this.validator){finalMatcher matcher =this.validator.matcher(valueStr);return matcher.matches();}returntrue;}/**
* {@inheritDoc}
*/@Overridepublicvoid matchEx(finalString valueStr)throwsArgumentException{if(false==this.match(valueStr)){if(null== valueStr){throwArgumentException.createEx(ErrorCode.PARAM_MISSED,"The value must not be null",this.name);}throwArgumentException.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.
*/@Overridepublic T parse(finalString valueStr)throwsArgumentException,InternalServiceException{if(null== valueStr){returnnull;}this.matchEx(valueStr);if(String.class==this.type){returnthis.type.cast(valueStr);}if(Boolean.class==this.type){returnthis.type.cast(Boolean.valueOf(("1".equals(valueStr))||Boolean.valueOf(valueStr)));}try{if(Integer.class==this.type){returnthis.type.cast(Integer.valueOf(valueStr));}if(Long.class==this.type){returnthis.type.cast(Long.valueOf(valueStr));}}catch(finalNumberFormatException e){throwArgumentException.createEx(ErrorCode.PARAM_BAD,"The value cannot be parsed as "+this.type.getSimpleName().toLowerCase()+".",this.name);}returnthis.parseOther(valueStr);}/**
* Field access for {@link #name}.
*
* @return the value of {@link #name}.
*/@OverridepublicString getName(){returnthis.name;}/**
* Field access for {@link #type}.
*
* @return the value of {@link #type}.
*/@OverridepublicClass<T> getType(){returnthis.type;}/**
* {@inheritDoc}
*/@OverridepublicfinalString toString(finalObject value)throwsInternalServiceException{if(false==this.type.isAssignableFrom(value.getClass())){thrownewInternalServiceException(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){returnString.valueOf(value);}if(Boolean.class==this.type){returnBoolean.TRUE.equals(value)?"1":"0";}returnthis.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(finalString valueStr)throwsArgumentException,InternalServiceException{thrownewInternalServiceException(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()}
*/protectedString toStringOther(finalObject value)throwsInternalServiceException{thrownewInternalServiceException(ErrorCode.PANIC,"ParameterImpl.toStringOther(): Unsupported parameter type: "+this.type.getName());}}
// enum A { a, b, c }staticfinalSet<Short> enumA =newLinkedHashSet<>(Arrays.asList(newShort[]{'a','b','c'}));// enum B extends A { d }staticfinalSet<Short> enumB =newLinkedHashSet<>(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:thrownewIllegalStateException("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: "+newString(Character.toChars(a)));break;default:thrownewIllegalStateException("This should never happen.");}}
Ab Java 7 können Sie dasselbe sogar tun mit String:
// enum A { BACKWARDS, FOREWARDS, STANDING }staticfinalSet<String> enumA =newLinkedHashSet<>(Arrays.asList(newString[]{"BACKWARDS","FOREWARDS","STANDING"}));// enum B extends A { JUMP }staticfinalSet<String> enumB =newLinkedHashSet<>(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:thrownewIllegalStateException("This should never happen.");}}
PrimaryColours
; es ist vernünftig zu wollen Super -Klasse dies zu EnumPrimaryAndPastelColours
durch 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:PrimaryAndPastelColours
und anschließend Unter -Klasse dies:PrimaryColours
(die Hierarchie im Auge behalten). Java wird das aber auch nicht zulassen.Antworten:
Nein, das können Sie in Java nicht. Abgesehen von allem anderen
d
wäre es dann vermutlich eine Instanz vonA
(angesichts der normalen Idee von "Erweitert"), aber Benutzer, die nur davon wussten,A
wü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.
quelle
Object->A->B
anstelle vonObject->A->B extends Object
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.
Alternativ könnten wir eine offene Schnittstelle für den Wochentag haben:
Oder wir könnten die beiden Ansätze kombinieren:
quelle
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.
quelle
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:
Der resultierende Code aus Javap:
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!
quelle
kann geschrieben werden als:
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:
Beispiel:
Fügen Sie einige erweiterte hinzu:
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:
Es kann nützlich sein für:
quelle
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:
Dann können Sie versuchen, ein Handbuch zu erstellen, das folgendermaßen erweitert wird:
Natürlich müssen Sie jedes Mal, wenn Sie eine Konstante erweitern müssen, Ihre SubEnum-Dateien ändern.
quelle
Falls Sie es verpasst haben, gibt es ein Kapitel in dem ausgezeichneten Buch " Java Effective, 2nd Edition " von Joshua Bloch .
Extrahieren Sie hier .
Nur die Schlussfolgerung:
quelle
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:
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.
quelle
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:
Basisaufzählung mit Überprüfungsmethode:
Erweiterungsbeispiel:
quelle
Basierend auf der Antwort von @Tom Hawtin - Tackline fügen wir Switch-Unterstützung hinzu.
quelle
valueOf()
Methode?Day
, dievalueOf()
dann eine Methode hatswitch(Day.valueOf())
, die durchWeekDay, WeekEndDay
Aufzählungen implementiert wird .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:
Seien Sie vorsichtig, Haustiere sind keine unveränderlichen Sammlungen. Vielleicht möchten Sie Guava oder Java9 für mehr Sicherheit verwenden.
quelle
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:
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 ...
quelle
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.
quelle
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.
Die implementierende ENUM-Basisklasse.
Die untergeordnete ENUM, die von der Basisklasse "erbt".
Zum Schluss das generische ParameterImpl, um einige Dienstprogramme hinzuzufügen.
quelle
Mein Weg zum Code wäre wie folgt:
LinkedHashSet
sieht vor, dass jeder Eintrag nur einmal existiert und dass ihre Reihenfolge erhalten bleibt. Wenn die Reihenfolge keine Rolle spielt, können SieHashSet
stattdessen verwenden. Der folgende Code ist in Java nicht möglich :Der Code kann wie folgt geschrieben werden:
Ab Java 7 können Sie dasselbe sogar tun mit
String
:Verwenden des Enum-Ersatzes:
quelle