Powodem tego jest przetestowanie sytuacji, w której istnieje niepoprawna wartość wyliczenia bez wprowadzenia niepoprawnej wartości wyliczenia w źródle rdzenia.
Archimedes Trajano
Tak, przykład „językowej” czystości. Myślę, że to, co jest pożądane, to pomysł „księgowości” na oszczędzanie siły roboczej zestawu liczb całkowitych z automatyczną inkrementacją, taki jak w C ++, aby można było rozpocząć nowy zestaw jako rozszerzenie starego zestawu, zaczynając od 1+ ostatniej wartości wcześniejszy zestaw, a jeśli wpisy są nazwane, dziedziczą nazwy z „wspólnego podzbioru”. Chociaż wyliczanie języka Java ma w tym kilka fajnych rzeczy, brakuje mu prostej automatycznej automatycznej inkrementacji liczb całkowitych, która zapewnia pomoc wyliczania w C ++.
peterk
4
W rzeczywistości, kiedy rozszerzasz swój enum o nowe wartości, tworzysz nie podklasę, ale nadklasę. Możesz używać bazowych wartości wyliczeniowych wszędzie zamiast „rozszerzonego” wyliczenia, ale nie odwrotnie, więc zgodnie z zasadą podstawienia Liskowa, rozszerzony wyliczanie jest nadklasą wyliczenia podstawowego.
Ilya
@Ilya ... tak, to prawda. Zwracam uwagę, że pytanie ma konkretne przypadki użycia w świecie rzeczywistym. Czysto teoretycznie, za podstawę Enum z: PrimaryColours; zasadne jest chcą Super -class to Enum PrimaryAndPastelColoursprzez dodanie nowych nazw kolorów. Liskov wciąż jest słoniem w pokoju. Więc dlaczego nie zacząć od bazy Enum z: AllMyColours- a potem jeden może sub -class wszystkich kolorach do: PrimaryAndPastelColoursa następnie sub -class to: PrimaryColours(zachowując hierarchię w umyśle). Java też na to nie pozwala.
będzie
Odpowiedzi:
450
Nie, nie możesz tego zrobić w Javie. Oprócz czegokolwiek innego, dprawdopodobnie byłby to przypadek A(biorąc pod uwagę normalną ideę „rozszerzenia”), ale użytkownicy, którzy tylko wiedzieli o Atym, nie wiedzieliby o tym - co pokonuje sens wyliczenia będącego dobrze znanym zestawem wartości.
Jeśli możesz nam powiedzieć więcej o tym, jak chcesz z tego korzystać , możemy zaproponować alternatywne rozwiązania.
Wszystkie wyliczenia domyślnie rozszerzają java.lang.Enum. Ponieważ Java nie obsługuje wielokrotnego dziedziczenia, wyliczanie nie może rozszerzać niczego innego.
ofiarować
9
Powodem, dla którego chcę rozszerzyć, jest to, że chciałbym mieć klasę podstawową o nazwie np. IntEnum, która wygląda następująco: stackoverflow.com/questions/1681976/enum-with-int-value-in-java/… . Wówczas wszystkie moje wyliczenia mogłyby go rozszerzyć ... w tym przypadku po prostu korzystałem z dziedziczenia, a zatem nie musiałbym często kopiować tego kodu „wyliczania int”. Jestem nowy w Javie i pochodzę z C # i mam nadzieję, że coś mi umknęło. Moje obecne zdanie jest takie, że wyliczenia Java są uciążliwe w porównaniu do C #.
Tyler Collier
30
@Tyler: C # enum to tylko nazwy związane z liczbami, bez automatycznej weryfikacji lub czegokolwiek . Wyliczenia IMO to jeden kawałek Java, który jest rzeczywiście lepszy niż C #.
Jon Skeet
21
Nie zgadzam się z @JonSkeet tutaj. W moim przypadku chciałbym oddzielić całą nieprzyjemną logikę w moim wielkim wyliczeniu, ukryć logikę i zdefiniować czyste wyliczenie, które rozszerza drugą, która jest ukryta. Wyliczenia z dużą ilością logiki przewyższają pomysł zadeklarowania czystych zmiennych, więc nie trzeba deklarować setek zmiennych ciągów statycznych, więc klasa z 5 wyliczeniami nie staje się nieczytelna i zbyt duża w liniach. Nie chcę też, aby inni programiści zajmowali się kopiowaniem i wklejaniem tego spokoju kodu dla następnego projektu i zamiast tego rozszerzali podstawową wartość ... to ma dla mnie sens ...
mmm
43
@givanse ... nie zgadzam się z tobą w kwestii domniemanego rozszerzenia java.lang.Enum jest przyczyną niedziedziczenia, ponieważ każda klasa w java również domyślnie dziedziczy klasę Object, ale może odziedziczyć inną klasę, tak jak wtedy do hierarchii, jak Object->A->BzamiastObject->A->B extends Object
mickeymoon
317
Wyliczenia stanowią pełne wyliczenie możliwych wartości. Tak więc (niepomocna) odpowiedź brzmi „nie”.
Przykładem prawdziwego problemu są dni powszednie, dni weekendowe i związek zawodowy, dni tygodnia. Możemy zdefiniować wszystkie dni w ciągu tygodnia, ale wtedy nie będziemy w stanie reprezentować właściwości specjalnych dla dni powszednich i weekendowych.
Co możemy zrobić, to mieć trzy typy wyliczeń z mapowaniem między dniami powszednimi / weekendowymi i dniami tygodnia.
Czy nie ma z tym problemu? Instrukcja switch nie będzie działać na interfejsie, ale działa na zwykłym wyliczaniu. Niedziałanie z przełącznikiem zabija jedną z ładniejszych rzeczy w wyliczeniach.
Manius
9
Myślę, że może być z tym kolejny problem. Nie ma równości między Weekday.MON i DayOfWeek.MON. Czy to nie jest kolejna wielka zaleta enum? Nie mam lepszego rozwiązania, po prostu zdaję sobie z tego sprawę, gdy próbuję znaleźć najlepszą odpowiedź. Brak możliwości użycia == zmusza nieco rękę.
Snekse
2
@Crusader tak, to jest właśnie kompromis. Jeśli chcesz czegoś rozwijanego, nie możesz mieć stałych instrukcji przełączania, jeśli chcesz zestawu znanych znanych wartości, tautologicznie nie możesz mieć czegoś rozwijanego.
djechlin
3
Przechodząc od wyliczenia do interfejsu, tracisz również statyczne wywołanie wartości (). Utrudnia to refaktoryzację, szczególnie jeśli zdecydujesz się rozszerzyć swój wyliczenie i dodać interfejs jako barierę abstrakcji do ustalonego wyliczenia.
Joshua Goldberg
4
Takie podejście do wyliczania wyliczenia z interfejsu jest używane przez API Java 1.7, np. Java.nio.file.Files.write () przyjmuje tablicę OpenOption jako ostatni argument. OpenOption jest interfejsem, ale kiedy wywołujemy tę funkcję, zwykle przekazujemy stałą wyliczania StandardOpenOption, która jest pochodną OpenOption. Ma to tę zaletę, że jest rozszerzalne, ale ma również wady. Implementacja cierpi z powodu faktu, że OpenOption jest interfejsem. Tworzy zestaw HashSet <OpenOption> na podstawie przekazanej tablicy, kiedy mógł stworzyć bardziej energooszczędny i zajmujący dużo czasu EnumSet. I nie można użyć przełącznika.
Obejmuje to utworzenie interfejsu i korzystanie z tego, w którym obecnie używasz wyliczenia. Następnie spraw, aby enum zaimplementował interfejs. Możesz dodać więcej stałych, sprawiając, że nowe wyliczenie rozszerzy również interfejs.
Warto przywołać ich użycie fabrycznej metody w interfejsie. Świetny sposób na dzielenie wspólnej funkcjonalności między powiązanymi Enums, biorąc pod uwagę, że rozszerzenie nie jest realnym rozwiązaniem.
Tim Clemons
8
Czy możesz podać więcej szczegółów (kod :)) na temat tego wzoru?
Dherik,
3
Ten wzorzec nie pozwala na rozszerzenie wartości wyliczenia. O to właśnie chodzi w pytaniu.
Eria,
55
Pod pokrywami twój ENUM jest zwykłą klasą generowaną przez kompilator. Ta wygenerowana klasa rozszerza się java.lang.Enum. Technicznym powodem, dla którego nie można rozszerzyć wygenerowanej klasy, jest to, że wygenerowana klasa jest final. W tym temacie omówiono koncepcyjne przyczyny ostateczności. Ale dodam mechanikę do dyskusji.
Oto wyliczenie testowe:
publicenum TEST {
ONE, TWO, THREE;}
Kod wynikowy z 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);}
Możliwe, że możesz wpisać tę klasę samodzielnie i upuścić „końcowy”. Ale kompilator uniemożliwia bezpośrednie rozszerzenie pliku „java.lang.Enum”. Możesz zdecydować, aby NIE rozszerzyć java.lang.Enum, ale wtedy twoja klasa i klasy pochodne nie byłyby instancją java.lang.Enum ... co może tak naprawdę nie mieć dla ciebie znaczenia!
Nie ma w nim kodu. Program „javap” pokazuje pusty blok.
ChrisCantrell,
Dziwnie go mieć, jeśli nic nie robi, prawda?
ukończył
4
Masz rację! Mój błąd. NIE jest to pusty blok kodu. Jeśli uruchomisz „javap -c”, zobaczysz rzeczywisty kod w bloku statycznym. Blok statyczny tworzy wszystkie instancje ENUM (tutaj JEDEN, DWA i TRZY). Przepraszam za to.
ChrisCantrell,
1
Dziękuję za stwierdzenie prostego faktu: ponieważ java.lang.Enum zostaje uznany za ostateczny.
Benjamin
26
enum A {a,b,c}enum B extends A {d}/*B is {a,b,c,d}*/
można zapisać jako:
publicenumAll{
a (ClassGroup.A,ClassGroup.B),
b (ClassGroup.A,ClassGroup.B),
c (ClassGroup.A,ClassGroup.B),
d (ClassGroup.B)...
ClassGroup.B.getMembers () zawiera {a, b, c, d}
Jak może być przydatny: Powiedzmy, że chcemy czegoś takiego: Mamy wydarzenia i używamy wyliczeń. Te wyliczenia można pogrupować przez podobne przetwarzanie. Jeśli mamy operację z wieloma elementami, niektóre zdarzenia rozpoczynają działanie, niektóre są tylko krokami, a inne kończą działanie. Aby zebrać taką operację i uniknąć długiej skrzynki przełączników, możemy zgrupować je jak w przykładzie i użyć:
Powyżej, jeśli mamy pewne błędy (myEvent.is (State_StatusGroup.FAIL)), to iterując poprzednie zdarzenia, możemy łatwo sprawdzić, czy musimy cofnąć przelew pieniędzy poprzez:
ciekawe, moglibyśmy również użyć bardzo enum toString (), a na końcu porównać ciągi; i aby użyć przełącznika, musielibyśmy po prostu rzucić obiekt na znany wyliczenie; jedynym problemem byłyby 2 programistów rozszerzających i tworzących identyczny identyfikator enum, a później próbujących scalić oba kody :), teraz myślę, że rozumiem, dlaczego enum powinno pozostać nierozszerzalne.
Aquarius Power
11
Na wypadek, gdybyś go przeoczył, jest rozdział w doskonałej książce Joshua Blocha „ Java Effective, 2nd edition ”.
Rozdział 6 - Wyliczenia i adnotacje
Punkt 34: Emuluj rozszerzalne wyliczenia za pomocą interfejsów
Niewielką wadą zastosowania interfejsów do emulacji rozszerzalnych wyliczeń jest to, że implementacje nie mogą być dziedziczone z jednego rodzaju wyliczenia na inny. W przypadku naszego przykładu Operacja logika do przechowywania i pobierania symbolu związanego z operacją jest duplikowana w BasicOperation i ExtendedOperation. W tym przypadku nie ma to znaczenia, ponieważ bardzo mało kodu jest duplikowane. Gdyby była większa ilość współużytkowanej funkcjonalności, można by ją zamknąć w klasie pomocniczej lub statycznej metodzie pomocniczej, aby wyeliminować duplikację kodu.
Podsumowując, chociaż nie można napisać rozszerzalnego typu wyliczenia, można go emulować, pisząc interfejs zgodny z podstawowym typem wyliczenia, który implementuje interfejs. Pozwala to klientom pisać własne wyliczenia, które implementują interfejs. Tych wyliczeń można następnie używać wszędzie tam, gdzie można zastosować podstawowy typ wyliczania, zakładając, że interfejsy API są napisane pod względem interfejsu.
Staram się unikać wyliczeń, ponieważ nie są one rozszerzalne. Aby pozostać na przykładzie OP, jeśli A znajduje się w bibliotece, a B we własnym kodzie, nie można rozszerzyć A, jeśli jest to wyliczenie. Tak czasami zastępuję wyliczenia:
// 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;};}
Jest kilka dołów, których należy unikać, zobacz komentarze w kodzie. W zależności od potrzeb jest to solidna, rozszerzalna alternatywa dla enum.
może być w porządku, jeśli potrzebujesz tylko porządków na instancje. Ale wyliczenia mają również właściwość nazwy, która jest dość przydatna.
Inor
6
W ten sposób poprawiam wzorzec dziedziczenia wyliczania za pomocą sprawdzania czasu wykonywania w inicjalizatorze statycznym. Do BaseKind#checkEnumExtenderkontroli, że „rozszerzenie” enum deklaruje wartości wyliczenia podstawy w dokładnie taki sam sposób, #name()i #ordinal()pozostają w pełni kompatybilne.
Nadal istnieje deklaracja kopiuj-wklej dla deklarowania wartości, ale program szybko się nie powiedzie, jeśli ktoś doda lub zmodyfikuje wartość w klasie podstawowej bez aktualizacji rozszerzających.
Typowe zachowanie różnych wyliczeń, które się wzajemnie rozszerzają:
publicinterfaceKind{/**
* Let's say we want some additional member.
*/String description();/**
* Standard {@code Enum} method.
*/String name();/**
* Standard {@code Enum} method.
*/int ordinal();}
Podstawowy wyliczenie, z metodą weryfikacyjną:
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 Chodzi o to, że polegamy na interfejsie, Dayktóry ma metodę, valueOf()a następnie switch(Day.valueOf())jest implementowany przez WeekDay, WeekEndDaywyliczenia.
Khaled Lela,
3
Sugeruję, abyś odwrócił podejście.
Zamiast rozszerzać istniejące wyliczenie, utwórz większe i utwórz jego podzbiór. Na przykład, jeśli miałeś wyliczenie o nazwie PET i chciałeś rozszerzyć go na ZWIERZĘTA, powinieneś to zrobić:
publicenum ANIMAL {
WOLF,CAT, DOG
}EnumSet<ANIMAL> pets =EnumSet.of(ANIMAL.CAT, ANIMAL.DOG);
Uważaj, zwierzęta nie są niezmiennymi kolekcjami, możesz chcieć używać Guava lub Java9 dla większego bezpieczeństwa.
Mając sam ten sam problem, chciałbym opublikować swoją perspektywę. Myślę, że istnieje kilka czynników motywujących do zrobienia czegoś takiego:
Chcesz mieć powiązane kody wyliczeniowe, ale w różnych klasach. W moim przypadku miałem klasę podstawową z kilkoma kodami zdefiniowanymi w powiązanym wyliczeniu. W pewnym późniejszym terminie (dziś!) Chciałem zapewnić nową funkcjonalność klasie podstawowej, co oznaczało także nowe kody dla wyliczenia.
Klasa pochodna obsługuje zarówno wyliczanie klas podstawowych, jak i własne. Brak zduplikowanych wartości wyliczeniowych! Tak więc: jak uzyskać wyliczenie dla podklasy, która obejmuje wyliczenie jego rodzica wraz z jego nowymi wartościami.
Używanie interfejsu tak naprawdę go nie wycina: możesz przypadkowo uzyskać zduplikowane wartości wyliczeniowe. Nie jest pożądane.
Skończyło się na tym, że połączyłem wyliczenia: to gwarantuje, że nie będzie żadnych zduplikowanych wartości, kosztem mniej ścisłego powiązania z powiązaną klasą. Uznałem jednak, że duplikat jest moim głównym zmartwieniem ...
Jako pomoc w zrozumieniu, dlaczego rozszerzenie Enum nie jest rozsądne na poziomie implementacji języka, aby rozważyć, co by się stało, gdybyś przekazał instancję rozszerzonego Enum do procedury, która rozumie tylko podstawowe Enum. Przełącznik obiecany przez kompilator obejmujący wszystkie przypadki w rzeczywistości nie obejmowałby tych rozszerzonych wartości Enum.
To dodatkowo podkreśla, że wartości Java Enum nie są liczbami całkowitymi, takimi jak C, na przykład: aby użyć Java Enum jako indeksu tablicowego, należy jawnie poprosić o jego element ordinal (), aby nadać java Enum dowolną wartość całkowitą, którą należy dodać jawne pole dla tego i odwołanie do tego nazwanego członka.
To nie jest komentarz dotyczący chęci OP, tylko to, dlaczego Java nigdy tego nie zrobi.
W nadziei, że to eleganckie rozwiązanie mojego kolegi jest nawet widoczne w tym długim poście, chciałbym podzielić się tym podejściem do podklas, które jest zgodne z podejściem interfejsu i nie tylko.
Pamiętaj, że używamy tutaj niestandardowych wyjątków i ten kod nie zostanie skompilowany, chyba że zastąpisz go wyjątkami.
Dokumentacja jest obszerna i mam nadzieję, że będzie zrozumiała dla większości z was.
Interfejs, który musi implementować każdy podklasowany wyliczanie.
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;}
Implementująca klasa bazowa ENUM.
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);}}
Podklasowane ENUM, które „dziedziczy” po klasie podstawowej.
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);}}
Wreszcie ogólny ParameterImpl, aby dodać niektóre narzędzia.
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', '♯', '♭' }));}
LinkedHashSetzapewnia zarówno, że każdy wpis istnieje tylko raz, i że ich kolejność jest zachowana. Jeśli kolejność nie ma znaczenia, możesz użyć HashSetzamiast tego. Poniższy kod nie jest możliwy w Javie:
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.");}}
Kod można zapisać w następujący sposób:
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.");}}
Począwszy od Java 7, możesz nawet zrobić to samo z 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");}
Korzystanie z zastępowania enum:
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
; zasadne jest chcą Super -class to EnumPrimaryAndPastelColours
przez dodanie nowych nazw kolorów. Liskov wciąż jest słoniem w pokoju. Więc dlaczego nie zacząć od bazy Enum z:AllMyColours
- a potem jeden może sub -class wszystkich kolorach do:PrimaryAndPastelColours
a następnie sub -class to:PrimaryColours
(zachowując hierarchię w umyśle). Java też na to nie pozwala.Odpowiedzi:
Nie, nie możesz tego zrobić w Javie. Oprócz czegokolwiek innego,
d
prawdopodobnie byłby to przypadekA
(biorąc pod uwagę normalną ideę „rozszerzenia”), ale użytkownicy, którzy tylko wiedzieli oA
tym, nie wiedzieliby o tym - co pokonuje sens wyliczenia będącego dobrze znanym zestawem wartości.Jeśli możesz nam powiedzieć więcej o tym, jak chcesz z tego korzystać , możemy zaproponować alternatywne rozwiązania.
źródło
Object->A->B
zamiastObject->A->B extends Object
Wyliczenia stanowią pełne wyliczenie możliwych wartości. Tak więc (niepomocna) odpowiedź brzmi „nie”.
Przykładem prawdziwego problemu są dni powszednie, dni weekendowe i związek zawodowy, dni tygodnia. Możemy zdefiniować wszystkie dni w ciągu tygodnia, ale wtedy nie będziemy w stanie reprezentować właściwości specjalnych dla dni powszednich i weekendowych.
Co możemy zrobić, to mieć trzy typy wyliczeń z mapowaniem między dniami powszednimi / weekendowymi i dniami tygodnia.
Alternatywnie, możemy mieć otwarty interfejs na dzień tygodnia:
Lub możemy połączyć dwa podejścia:
źródło
Zalecanym rozwiązaniem tego problemu jest rozszerzalny wzór wyliczenia .
Obejmuje to utworzenie interfejsu i korzystanie z tego, w którym obecnie używasz wyliczenia. Następnie spraw, aby enum zaimplementował interfejs. Możesz dodać więcej stałych, sprawiając, że nowe wyliczenie rozszerzy również interfejs.
źródło
Pod pokrywami twój ENUM jest zwykłą klasą generowaną przez kompilator. Ta wygenerowana klasa rozszerza się
java.lang.Enum
. Technicznym powodem, dla którego nie można rozszerzyć wygenerowanej klasy, jest to, że wygenerowana klasa jestfinal
. W tym temacie omówiono koncepcyjne przyczyny ostateczności. Ale dodam mechanikę do dyskusji.Oto wyliczenie testowe:
Kod wynikowy z javap:
Możliwe, że możesz wpisać tę klasę samodzielnie i upuścić „końcowy”. Ale kompilator uniemożliwia bezpośrednie rozszerzenie pliku „java.lang.Enum”. Możesz zdecydować, aby NIE rozszerzyć java.lang.Enum, ale wtedy twoja klasa i klasy pochodne nie byłyby instancją java.lang.Enum ... co może tak naprawdę nie mieć dla ciebie znaczenia!
źródło
można zapisać jako:
Jak może być przydatny: Powiedzmy, że chcemy czegoś takiego: Mamy wydarzenia i używamy wyliczeń. Te wyliczenia można pogrupować przez podobne przetwarzanie. Jeśli mamy operację z wieloma elementami, niektóre zdarzenia rozpoczynają działanie, niektóre są tylko krokami, a inne kończą działanie. Aby zebrać taką operację i uniknąć długiej skrzynki przełączników, możemy zgrupować je jak w przykładzie i użyć:
Przykład:
Dodaj bardziej zaawansowane:
Powyżej, jeśli mamy pewne błędy (myEvent.is (State_StatusGroup.FAIL)), to iterując poprzednie zdarzenia, możemy łatwo sprawdzić, czy musimy cofnąć przelew pieniędzy poprzez:
Może być przydatny do:
źródło
Oto sposób, w jaki znalazłem, jak rozszerzyć wyliczenie na inny wyliczenie, jest bardzo proste podejście:
Zakładając, że masz wyliczenie ze wspólnymi stałymi:
możesz spróbować wykonać instrukcję rozszerzającą w następujący sposób:
oczywiście za każdym razem, gdy musisz rozszerzyć stałą, musisz zmodyfikować swoje pliki SubEnum.
źródło
Na wypadek, gdybyś go przeoczył, jest rozdział w doskonałej książce Joshua Blocha „ Java Effective, 2nd edition ”.
Wyodrębnij tutaj .
Właśnie wniosek:
źródło
Staram się unikać wyliczeń, ponieważ nie są one rozszerzalne. Aby pozostać na przykładzie OP, jeśli A znajduje się w bibliotece, a B we własnym kodzie, nie można rozszerzyć A, jeśli jest to wyliczenie. Tak czasami zastępuję wyliczenia:
Jest kilka dołów, których należy unikać, zobacz komentarze w kodzie. W zależności od potrzeb jest to solidna, rozszerzalna alternatywa dla enum.
źródło
W ten sposób poprawiam wzorzec dziedziczenia wyliczania za pomocą sprawdzania czasu wykonywania w inicjalizatorze statycznym. Do
BaseKind#checkEnumExtender
kontroli, że „rozszerzenie” enum deklaruje wartości wyliczenia podstawy w dokładnie taki sam sposób,#name()
i#ordinal()
pozostają w pełni kompatybilne.Nadal istnieje deklaracja kopiuj-wklej dla deklarowania wartości, ale program szybko się nie powiedzie, jeśli ktoś doda lub zmodyfikuje wartość w klasie podstawowej bez aktualizacji rozszerzających.
Typowe zachowanie różnych wyliczeń, które się wzajemnie rozszerzają:
Podstawowy wyliczenie, z metodą weryfikacyjną:
Próbka rozszerzenia:
źródło
W oparciu o @Tom Hawtin - szybką odpowiedź dodajemy obsługę przełączników,
źródło
valueOf()
metody?Day
który ma metodę,valueOf()
a następnieswitch(Day.valueOf())
jest implementowany przezWeekDay, WeekEndDay
wyliczenia.Sugeruję, abyś odwrócił podejście.
Zamiast rozszerzać istniejące wyliczenie, utwórz większe i utwórz jego podzbiór. Na przykład, jeśli miałeś wyliczenie o nazwie PET i chciałeś rozszerzyć go na ZWIERZĘTA, powinieneś to zrobić:
Uważaj, zwierzęta nie są niezmiennymi kolekcjami, możesz chcieć używać Guava lub Java9 dla większego bezpieczeństwa.
źródło
Mając sam ten sam problem, chciałbym opublikować swoją perspektywę. Myślę, że istnieje kilka czynników motywujących do zrobienia czegoś takiego:
Używanie interfejsu tak naprawdę go nie wycina: możesz przypadkowo uzyskać zduplikowane wartości wyliczeniowe. Nie jest pożądane.
Skończyło się na tym, że połączyłem wyliczenia: to gwarantuje, że nie będzie żadnych zduplikowanych wartości, kosztem mniej ścisłego powiązania z powiązaną klasą. Uznałem jednak, że duplikat jest moim głównym zmartwieniem ...
źródło
Jako pomoc w zrozumieniu, dlaczego rozszerzenie Enum nie jest rozsądne na poziomie implementacji języka, aby rozważyć, co by się stało, gdybyś przekazał instancję rozszerzonego Enum do procedury, która rozumie tylko podstawowe Enum. Przełącznik obiecany przez kompilator obejmujący wszystkie przypadki w rzeczywistości nie obejmowałby tych rozszerzonych wartości Enum.
To dodatkowo podkreśla, że wartości Java Enum nie są liczbami całkowitymi, takimi jak C, na przykład: aby użyć Java Enum jako indeksu tablicowego, należy jawnie poprosić o jego element ordinal (), aby nadać java Enum dowolną wartość całkowitą, którą należy dodać jawne pole dla tego i odwołanie do tego nazwanego członka.
To nie jest komentarz dotyczący chęci OP, tylko to, dlaczego Java nigdy tego nie zrobi.
źródło
W nadziei, że to eleganckie rozwiązanie mojego kolegi jest nawet widoczne w tym długim poście, chciałbym podzielić się tym podejściem do podklas, które jest zgodne z podejściem interfejsu i nie tylko.
Pamiętaj, że używamy tutaj niestandardowych wyjątków i ten kod nie zostanie skompilowany, chyba że zastąpisz go wyjątkami.
Dokumentacja jest obszerna i mam nadzieję, że będzie zrozumiała dla większości z was.
Interfejs, który musi implementować każdy podklasowany wyliczanie.
Implementująca klasa bazowa ENUM.
Podklasowane ENUM, które „dziedziczy” po klasie podstawowej.
Wreszcie ogólny ParameterImpl, aby dodać niektóre narzędzia.
źródło
Mój sposób na kodowanie wyglądałby następująco:
LinkedHashSet
zapewnia zarówno, że każdy wpis istnieje tylko raz, i że ich kolejność jest zachowana. Jeśli kolejność nie ma znaczenia, możesz użyćHashSet
zamiast tego. Poniższy kod nie jest możliwy w Javie:Kod można zapisać w następujący sposób:
Począwszy od Java 7, możesz nawet zrobić to samo z
String
:Korzystanie z zastępowania enum:
źródło