Причина для этого заключается в тестировании ситуации, когда существует недопустимое значение перечисления без введения недопустимого значения перечисления в основной источник.
Архимед Траяно
Да пример "языковой" чистоты. Я думаю, что желательна идея «экономного» трудосбережения, заключающаяся в автоматическом приращении набора целых чисел, который есть в C ++, так что вы можете начать новый набор как расширение старого набора, начиная с 1+ последнего значения предыдущий набор, и если записи названы, наследуют имена из «общего подмножества». Хотя в java enum есть несколько приятных моментов, в нем отсутствует простая автоматическая инкрементная целочисленная декларация помощи, которую предоставляет enum C ++.
peterk
4
На самом деле, когда вы расширяете свой enum новыми значениями, вы создаете не подкласс, а суперкласс. Вы можете использовать базовые значения перечисления везде вместо «расширенного» перечисления, но не наоборот, поэтому согласно принципу подстановки Лискова расширенное перечисление является суперклассом базового перечисления.
Илья
@ Илья ... да, это правда. Я подчеркиваю, что этот вопрос имеет определенные реальные варианты использования. Ради аргумента, рассмотрим базу Enum из: PrimaryColours; разумно хотеть супер -класса это Enum PrimaryAndPastelColours, добавляя новые названия цветов. Лисков все еще слон в комнате. Так почему бы не начать с базового Enum из: AllMyColours- И тогда можно было бы подклассировать все цвета в: PrimaryAndPastelColoursи впоследствии подклассить это в: PrimaryColours(имея в виду иерархию). Ява также не допустит этого.
будет
Ответы:
451
Нет, вы не можете сделать это в Java. Помимо всего прочего, dпредположительно это могло бы быть примером A(учитывая обычную идею «расширяет»), но пользователи, которые знали только об Aэтом, не знали бы об этом - что противоречит тому, что перечисление является известным набором ценности.
Если бы вы могли рассказать нам больше о том, как вы хотите использовать это, мы могли бы предложить альтернативные решения.
Все перечисления неявно расширяют java.lang.Enum. Поскольку Java не поддерживает множественное наследование, перечисление не может расширять что-либо еще.
Givanse
9
Причина, по которой я хочу расширить, состоит в том, что я хотел бы иметь базовый класс, например, IntEnum, который выглядит следующим образом: stackoverflow.com/questions/1681976/enum-with-int-value-in-java/… . Тогда все мои перечисления могут расширять его ... в этом случае просто получая выгоду от наследования, и, таким образом, мне не придется часто дублировать этот код "int-based enum". Я новичок в Java и пришел из C #, и я надеюсь, что что-то упустил. Мое текущее мнение таково, что перечисления Java - боль по сравнению с C #.
Тайлер Кольер
30
@Tyler: перечисления C # - это просто имена, связанные с числами, без автоматической проверки или чего-либо еще . Перечисления IMO - это один бит Java, который на самом деле лучше, чем C #.
Джон Скит
21
Не согласен с @JonSkeet здесь. В моем случае использования я хотел бы отделить всю неприятную логику в моем большом перечислении, и скрыть логику, и определить чистое перечисление, которое расширяет другое, которое скрыто. Перечисления с большим количеством логики превосходят идею объявления чистых переменных, поэтому вам не нужно объявлять сотни статических строковых переменных, чтобы класс с 5 перечислениями не стал нечитаемым и слишком большим в строках. Я не хочу, чтобы другие разработчики также занимались копированием и вставкой этого кода для следующего проекта и вместо этого расширяли base_enum ... это имеет смысл для меня ...
ммм
43
@givanse ... не согласен с вами в том, что касается неявного расширения java.lang.Enum является причиной ненаследования, поскольку каждый класс в java также неявно наследует класс Object, но может наследовать какой-то другой класс, как тогда в иерархию, а Object->A->BнеObject->A->B extends Object
mickeymoon
317
Перечисления представляют собой полный перечень возможных значений. Так что (бесполезный) ответ - нет.
В качестве примера реальной проблемы возьмем будние дни, выходные дни и, объединение, дни недели. Мы можем определить все дни в пределах дней недели, но тогда мы не сможем представлять свойства, особые для будних и выходных дней.
То, что мы могли бы сделать, это иметь три типа enum с отображением между днями недели / выходными днями и днями недели.
Есть ли проблема с этим? Оператор switch не работает на интерфейсе, но работает с обычным перечислением. Не работает без переключения убивает одну из приятных вещей в enums.
Маниус
9
Я думаю, что с этим может быть другая проблема. Нет равенства между Weekday.MON и DayOfWeek.MON. Разве это не другое большое преимущество перечислений? У меня нет лучшего решения, просто осознавая это, я пытаюсь найти лучший ответ. Отсутствие возможности использовать == заставляет руку немного.
Снексе
2
@ Крестоносец да, это именно компромисс. Если вы хотите что-то расширяемое, вы не можете иметь фиксированные операторы switch, если вы хотите набор фиксированных известных значений, у вас тавтологически не может быть что-то расширяемого.
Джечлин
3
Переходя от enum к интерфейсу, вы также теряете статический вызов values (). Это затрудняет рефакторинг, особенно если вы решили расширить свой перечисление и добавить интерфейс в качестве барьера абстракции для установленного перечисления.
Джошуа Голдберг
4
Этот подход получения enum из интерфейса используется API Java 1.7, например, java.nio.file.Files.write () принимает массив OpenOption в качестве последнего аргумента. OpenOption - это интерфейс, но когда мы вызываем эту функцию, мы обычно передаем константу перечисления StandardOpenOption, которая получается из OpenOption. Преимущество в том, что оно расширяемое, но также имеет и недостатки. Реализация страдает от того факта, что OpenOption является интерфейсом. Он создает HashSet <OpenOption> из переданного массива, когда он мог бы создать более эффективный по пространству и времени EnumSet. И он не может использовать переключатель.
Это включает в себя создание интерфейса и использование его там, где вы сейчас используете enum. Затем заставьте enum реализовать интерфейс. Вы можете добавить больше констант, сделав это новое перечисление также расширенным интерфейсом.
Стоит назвать их использование фабричного метода в интерфейсе. Отличный способ поделиться общей функциональностью между родственными Enums, учитывая, что расширение не является жизнеспособным решением.
Тим Клемонс
8
Можете ли вы предоставить более подробную информацию (код :)) об этом шаблоне?
Дерик
3
Этот шаблон не позволяет расширять значения перечисления. Какой вопрос в заданном вопросе.
Эрия
55
Под покровом ваш ENUM - просто обычный класс, сгенерированный компилятором. Этот сгенерированный класс расширяется java.lang.Enum. Техническая причина, по которой вы не можете расширить сгенерированный класс, заключается в том, что сгенерированный класс есть final. Концептуальные причины его окончательности обсуждаются в этой теме. Но я добавлю механику к обсуждению.
Вот тестовое перечисление:
publicenum TEST {
ONE, TWO, THREE;}
Полученный код из 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);}
Возможно, вы могли бы напечатать этот класс по своему усмотрению и отбросить «финал». Но компилятор не позволяет вам напрямую расширять "java.lang.Enum". Вы можете решить НЕ расширять java.lang.Enum, но тогда ваш класс и его производные классы не будут экземпляром java.lang.Enum ... что, в действительности, не имеет для вас никакого значения!
В нем нет кода. Программа "javap" показывает пустой блок.
ChrisCantrell,
Странно иметь его там, если он ничего не делает, не так ли?
soote
4
Вы правы! Моя ошибка. Это НЕ пустой блок кода. Если вы запустите "javap -c", то увидите статический код внутри статического блока. Статический блок создает все экземпляры ENUM (один, два и три здесь). Прости за это.
ChrisCantrell,
1
Спасибо за констатацию прямого факта: потому что java.lang.Enum объявлен финальным.
Бенджамин
26
enum A {a,b,c}enum B extends A {d}/*B is {a,b,c,d}*/
можно записать как:
publicenumAll{
a (ClassGroup.A,ClassGroup.B),
b (ClassGroup.A,ClassGroup.B),
c (ClassGroup.A,ClassGroup.B),
d (ClassGroup.B)...
ClassGroup.B.getMembers () содержит {a, b, c, d}
Чем это может быть полезно: допустим, мы хотим что-то вроде: у нас есть события и мы используем перечисления. Эти перечисления могут быть сгруппированы подобной обработкой. Если у нас есть работа со многими элементами, то некоторые события начинают работу, некоторые - просто шаг, а другие заканчивают операцию. Чтобы собрать такую операцию и избежать длинного случая переключения, мы можем сгруппировать их как в примере и использовать:
Выше, если у нас есть какой-то сбой (myEvent.is (State_StatusGroup.FAIL)), то итерируя по предыдущим событиям, мы можем легко проверить, должны ли мы отменить перевод денег:
Интересно, что мы можем использовать также очень enum toString (), и в конце сравнить строки; и чтобы использовать переключатель, нам просто нужно привести объект к известному перечислению; единственной проблемой было бы 2 разработчика, расширяющих и создающих идентичный идентификатор enum, а затем пытающихся объединить оба кода :), теперь я думаю, что понимаю, почему enum должен оставаться не расширяемым.
Незначительным недостатком использования интерфейсов для эмуляции расширяемых перечислений является то, что реализации не могут наследоваться от одного типа перечисления к другому. В нашем примере «Операция» логика для хранения и извлечения символа, связанного с операцией, дублируется в BasicOperation и ExtendedOperation. В этом случае это не имеет значения, потому что очень мало кода дублируется. Если бы было больше общих функций, вы могли бы инкапсулировать их во вспомогательный класс или статический вспомогательный метод, чтобы исключить дублирование кода.
Таким образом, хотя вы не можете написать расширяемый тип enum, вы можете эмулировать его, написав интерфейс, соответствующий базовому типу enum, который реализует интерфейс. Это позволяет клиентам писать свои собственные перечисления, которые реализуют интерфейс. Эти перечисления могут затем использоваться везде, где может использоваться базовый тип перечисления, при условии, что API написаны с точки зрения интерфейса.
Я стараюсь избегать перечислений, потому что они не расширяемы. Чтобы остаться на примере OP, если A находится в библиотеке, а B в вашем собственном коде, вы не можете расширить A, если это перечисление. Вот как я иногда заменяю перечисления:
// 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;};}
Есть некоторые ямы, которых следует избегать, см. Комментарии в коде. В зависимости от ваших потребностей, это солидная, расширяемая альтернатива перечислениям.
это может быть хорошо, если вам просто нужен порядковый номер для примеров. Но у перечислений также есть свойство name, которое довольно полезно.
INOR
6
Вот как я улучшаю шаблон наследования перечисления с помощью проверки во время выполнения в статическом инициализаторе. В BaseKind#checkEnumExtenderпроверяет , что «расширение» перечисление декларирует все значения базового перечисления точно таким же образом , так #name()и#ordinal() остается полностью совместимым.
Для объявления значений все еще используется копирование-вставка, но программа быстро завершается сбоем, если кто-то добавил или изменил значение в базовом классе без обновления расширяющих.
Общее поведение для различных перечислений, расширяющих друг друга:
publicinterfaceKind{/**
* Let's say we want some additional member.
*/String description();/**
* Standard {@code Enum} method.
*/String name();/**
* Standard {@code Enum} method.
*/int ordinal();}
Базовое перечисление с методом проверки:
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 Идея здесь мы зависим от интерфейса , Dayкоторый имеет метод , valueOf()то switch(Day.valueOf()), это реализуется WeekDay, WeekEndDayперечислений.
Халед Лела
3
Я предлагаю вам пойти другим путем.
Вместо расширения существующего перечисления создайте большее и создайте его подмножество. Например, если у вас было перечисление с именем PET, и вы хотели расширить его на ANIMAL, вы должны сделать это вместо этого:
publicenum ANIMAL {
WOLF,CAT, DOG
}EnumSet<ANIMAL> pets =EnumSet.of(ANIMAL.CAT, ANIMAL.DOG);
Будьте осторожны, домашние животные не являются неизменными коллекциями, вы можете использовать Guava или Java9 для большей безопасности.
Если бы у меня была такая же проблема, я бы хотел опубликовать свою точку зрения. Я думаю, что есть пара мотивирующих факторов для того, чтобы сделать что-то вроде этого:
Вы хотите иметь некоторые связанные коды перечисления, но в разных классах. В моем случае у меня был базовый класс с несколькими кодами, определенными в связанном перечислении. Позже (сегодня!) Я хотел предоставить некоторые новые функции базовому классу, что также означало новые коды для перечисления.
Производный класс будет поддерживать как перечисление базовых классов, так и его собственный. Нет повторяющихся значений перечисления! Итак: как получить перечисление для подкласса, которое включает перечисление его родителя вместе с его новыми значениями.
Использование интерфейса на самом деле не сокращает его: вы можете случайно получить повторяющиеся значения перечисления. Не желательно
В итоге я просто скомбинировал перечисления: это гарантирует, что не может быть повторяющихся значений, за счет того, что они менее тесно связаны с соответствующим классом. Но я решил, что проблема с дубликатами была моей главной заботой ...
Чтобы помочь понять, почему расширение Enum нецелесообразно на уровне реализации языка, подумать о том, что произойдет, если вы передали экземпляр расширенного Enum подпрограмме, которая понимает только базовый Enum. Переключатель, который обещал компилятор, охватил все случаи, фактически не охватывал бы эти расширенные значения Enum.
Это также подчеркивает, что значения Java Enum не являются целыми числами, такими как C, например: чтобы использовать Java Enum в качестве индекса массива, вы должны явно запросить его член ordinal (), чтобы дать java Enum произвольное целочисленное значение, которое вы должны добавить явное поле для этого и ссылка на указанный член.
Это не комментарий к желанию ОП, а просто то, почему Java никогда не собирается это делать.
В надежде, что это элегантное решение моего коллеги даже будет видно в этом длинном посте, я хотел бы поделиться этим подходом для создания подклассов, который следует за интерфейсным подходом и за его пределами.
Помните, что здесь мы используем пользовательские исключения, и этот код не будет компилироваться, если вы не замените его своими исключениями.
Документация обширна, и я надеюсь, что она понятна большинству из вас.
Интерфейс, который необходимо реализовать каждому подклассу enum.
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;}
Реализующий базовый класс 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);}}
Подкласс ENUM, который «наследует» от базового класса.
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);}}
Наконец, универсальный ParameterImpl для добавления некоторых утилит.
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', '♯', '♭' }));}
LinkedHashSetпредусматривает, что каждая запись существует только один раз, и что их порядок сохраняется. Если порядок не имеет значения, вы можете использовать HashSetвместо этого. Следующий код невозможен в Java:
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.");}}
Код можно написать следующим образом:
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.");}}
Начиная с Java 7 вы можете сделать то же самое с 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");}
Использование замены 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
; разумно хотеть супер -класса это EnumPrimaryAndPastelColours
, добавляя новые названия цветов. Лисков все еще слон в комнате. Так почему бы не начать с базового Enum из:AllMyColours
- И тогда можно было бы подклассировать все цвета в:PrimaryAndPastelColours
и впоследствии подклассить это в:PrimaryColours
(имея в виду иерархию). Ява также не допустит этого.Ответы:
Нет, вы не можете сделать это в Java. Помимо всего прочего,
d
предположительно это могло бы быть примеромA
(учитывая обычную идею «расширяет»), но пользователи, которые знали только обA
этом, не знали бы об этом - что противоречит тому, что перечисление является известным набором ценности.Если бы вы могли рассказать нам больше о том, как вы хотите использовать это, мы могли бы предложить альтернативные решения.
источник
Object->A->B
неObject->A->B extends Object
Перечисления представляют собой полный перечень возможных значений. Так что (бесполезный) ответ - нет.
В качестве примера реальной проблемы возьмем будние дни, выходные дни и, объединение, дни недели. Мы можем определить все дни в пределах дней недели, но тогда мы не сможем представлять свойства, особые для будних и выходных дней.
То, что мы могли бы сделать, это иметь три типа enum с отображением между днями недели / выходными днями и днями недели.
В качестве альтернативы мы могли бы иметь открытый интерфейс для дня недели:
Или мы могли бы объединить два подхода:
источник
Рекомендуемое решение для этого - расширяемый шаблон enum .
Это включает в себя создание интерфейса и использование его там, где вы сейчас используете enum. Затем заставьте enum реализовать интерфейс. Вы можете добавить больше констант, сделав это новое перечисление также расширенным интерфейсом.
источник
Под покровом ваш ENUM - просто обычный класс, сгенерированный компилятором. Этот сгенерированный класс расширяется
java.lang.Enum
. Техническая причина, по которой вы не можете расширить сгенерированный класс, заключается в том, что сгенерированный класс естьfinal
. Концептуальные причины его окончательности обсуждаются в этой теме. Но я добавлю механику к обсуждению.Вот тестовое перечисление:
Полученный код из javap:
Возможно, вы могли бы напечатать этот класс по своему усмотрению и отбросить «финал». Но компилятор не позволяет вам напрямую расширять "java.lang.Enum". Вы можете решить НЕ расширять java.lang.Enum, но тогда ваш класс и его производные классы не будут экземпляром java.lang.Enum ... что, в действительности, не имеет для вас никакого значения!
источник
можно записать как:
Чем это может быть полезно: допустим, мы хотим что-то вроде: у нас есть события и мы используем перечисления. Эти перечисления могут быть сгруппированы подобной обработкой. Если у нас есть работа со многими элементами, то некоторые события начинают работу, некоторые - просто шаг, а другие заканчивают операцию. Чтобы собрать такую операцию и избежать длинного случая переключения, мы можем сгруппировать их как в примере и использовать:
Пример:
Добавьте несколько более продвинутых:
Выше, если у нас есть какой-то сбой (myEvent.is (State_StatusGroup.FAIL)), то итерируя по предыдущим событиям, мы можем легко проверить, должны ли мы отменить перевод денег:
Это может быть полезно для:
источник
Вот способ, которым я нашел, как расширить перечисление в другое перечисление, это очень прямой подход:
Если у вас есть перечисление с общими константами:
тогда вы можете попытаться сделать руководство по расширению следующим образом:
Конечно, каждый раз, когда вам нужно расширить константу, вы должны изменить файлы SubEnum.
источник
Если вы пропустили это, в превосходной книге Джошуа Блоха « Java Effective, 2nd edition » есть глава .
Извлечь здесь .
Просто вывод:
источник
Я стараюсь избегать перечислений, потому что они не расширяемы. Чтобы остаться на примере OP, если A находится в библиотеке, а B в вашем собственном коде, вы не можете расширить A, если это перечисление. Вот как я иногда заменяю перечисления:
Есть некоторые ямы, которых следует избегать, см. Комментарии в коде. В зависимости от ваших потребностей, это солидная, расширяемая альтернатива перечислениям.
источник
Вот как я улучшаю шаблон наследования перечисления с помощью проверки во время выполнения в статическом инициализаторе. В
BaseKind#checkEnumExtender
проверяет , что «расширение» перечисление декларирует все значения базового перечисления точно таким же образом , так#name()
и#ordinal()
остается полностью совместимым.Для объявления значений все еще используется копирование-вставка, но программа быстро завершается сбоем, если кто-то добавил или изменил значение в базовом классе без обновления расширяющих.
Общее поведение для различных перечислений, расширяющих друг друга:
Базовое перечисление с методом проверки:
Пример расширения:
источник
На основе @Tom Hawtin - ответ tackline, мы добавили поддержку коммутатора,
источник
valueOf()
метода?Day
который имеет метод ,valueOf()
тоswitch(Day.valueOf())
, это реализуетсяWeekDay, WeekEndDay
перечислений.Я предлагаю вам пойти другим путем.
Вместо расширения существующего перечисления создайте большее и создайте его подмножество. Например, если у вас было перечисление с именем PET, и вы хотели расширить его на ANIMAL, вы должны сделать это вместо этого:
Будьте осторожны, домашние животные не являются неизменными коллекциями, вы можете использовать Guava или Java9 для большей безопасности.
источник
Если бы у меня была такая же проблема, я бы хотел опубликовать свою точку зрения. Я думаю, что есть пара мотивирующих факторов для того, чтобы сделать что-то вроде этого:
Использование интерфейса на самом деле не сокращает его: вы можете случайно получить повторяющиеся значения перечисления. Не желательно
В итоге я просто скомбинировал перечисления: это гарантирует, что не может быть повторяющихся значений, за счет того, что они менее тесно связаны с соответствующим классом. Но я решил, что проблема с дубликатами была моей главной заботой ...
источник
Чтобы помочь понять, почему расширение Enum нецелесообразно на уровне реализации языка, подумать о том, что произойдет, если вы передали экземпляр расширенного Enum подпрограмме, которая понимает только базовый Enum. Переключатель, который обещал компилятор, охватил все случаи, фактически не охватывал бы эти расширенные значения Enum.
Это также подчеркивает, что значения Java Enum не являются целыми числами, такими как C, например: чтобы использовать Java Enum в качестве индекса массива, вы должны явно запросить его член ordinal (), чтобы дать java Enum произвольное целочисленное значение, которое вы должны добавить явное поле для этого и ссылка на указанный член.
Это не комментарий к желанию ОП, а просто то, почему Java никогда не собирается это делать.
источник
В надежде, что это элегантное решение моего коллеги даже будет видно в этом длинном посте, я хотел бы поделиться этим подходом для создания подклассов, который следует за интерфейсным подходом и за его пределами.
Помните, что здесь мы используем пользовательские исключения, и этот код не будет компилироваться, если вы не замените его своими исключениями.
Документация обширна, и я надеюсь, что она понятна большинству из вас.
Интерфейс, который необходимо реализовать каждому подклассу enum.
Реализующий базовый класс ENUM.
Подкласс ENUM, который «наследует» от базового класса.
Наконец, универсальный ParameterImpl для добавления некоторых утилит.
источник
Мой способ кодирования будет следующим:
LinkedHashSet
предусматривает, что каждая запись существует только один раз, и что их порядок сохраняется. Если порядок не имеет значения, вы можете использоватьHashSet
вместо этого. Следующий код невозможен в Java:Код можно написать следующим образом:
Начиная с Java 7 вы можете сделать то же самое с
String
:Использование замены enum:
источник