Можно ли разделить перечисления на подклассы для добавления новых элементов?

Я хочу взять существующее перечисление и добавить к нему дополнительные элементы следующим образом:

enum A {a,b,c}

enum B extends A {d}

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

Возможно ли это в Java?


person Mike    schedule 12.09.2009    source источник
comment
Причина для этого заключается в том, чтобы протестировать ситуацию, когда имеется недопустимое значение перечисления, без введения недопустимого значения перечисления в основной источник.   -  person Archimedes Trajano    schedule 31.01.2013
comment
Да пример языковой чистоты. Я думаю, что желательно идея экономии труда в бухгалтерском учете об автоматическом увеличении набора целых чисел, как в С ++, чтобы вы могли начать новый набор как расширение старого набора, начиная с 1+ последнего значения предыдущего набора. , и если записи названы, наследуют имена из общего подмножества. Хотя в java enum есть некоторые приятные особенности, ему не хватает простой автоматизированной автоинкрементной справки по объявлению целых чисел, которую предоставляет перечисление C ++.   -  person peterk    schedule 23.02.2013
comment
Фактически, когда вы расширяете свое перечисление новыми значениями, вы создаете не подкласс, а суперкласс. Вы можете везде использовать значения базового перечисления вместо расширенного перечисления, но не наоборот, поэтому, согласно принципу подстановки Лискова, расширенное перечисление является суперклассом базового перечисления.   -  person Ilya    schedule 29.09.2014
comment
@ Илья ... да, это правда. Я отмечаю, что у этого вопроса есть определенные варианты использования в реальном мире. В качестве аргумента рассмотрим базовое перечисление: PrimaryColours; разумно захотеть супер -класс это в Enum PrimaryAndPastelColours, добавив новые названия цветов. Лисков по-прежнему слон в комнате. Так почему бы не начать с базового Enum из: AllMyColours - А затем можно можно sub классифицировать все цвета как: PrimaryAndPastelColours и впоследствии sub - классифицируйте это как: PrimaryColours (имея в виду иерархию). Но Java этого тоже не допускает.   -  person will    schedule 07.03.2017


Ответы (15)


Нет, на Java это невозможно. Помимо всего прочего, d предположительно был бы экземпляром A (учитывая обычную идею "расширений"), но пользователи, которые знали только о A, не знали бы об этом, что лишает смысла то, что перечисление является хорошо- известный набор значений.

Если бы вы могли рассказать нам больше о том, как вы хотите использовать это, мы могли бы предложить альтернативные решения.

person Jon Skeet    schedule 12.09.2009
comment
Краткое объяснение состоит в том, что существует существующее перечисление, в которое я хотел бы добавить больше элементов, не изменяя исходный. Я использовал «extends» как иллюстрацию идеи, я не собирался ограничивать себя ключевым словом. - person Mike; 12.09.2009
comment
Вы не можете сделать это именно по той причине, о которой я сказал ранее: перечисление должно быть хорошо известным набором значений. Если бы вы могли добавить к нему элементы, любой существующий код, использующий его, мог бы внезапно получить неприятный шок. - person Jon Skeet; 12.09.2009
comment
Все перечисления неявно расширяют java.lang.Enum. Поскольку Java не поддерживает множественное наследование, перечисление не может расширять что-либо еще. - person givanse; 24.10.2010
comment
Причина, по которой я хочу расширить, заключается в том, что я хотел бы иметь базовый класс, например, IntEnum, который выглядит так: stackoverflow.com/questions/1681976/enum-with-int-value-in-java/. Тогда все мои перечисления могли бы его расширить ... в этом случае просто получить выгоду от наследования, и, следовательно, мне не пришлось бы часто дублировать этот код перечисления на основе int. Я новичок в Java и пришел с C #, и я надеюсь, что что-то упустил. Мое текущее мнение таково, что перечисления Java - это боль по сравнению с C #. - person Tyler Collier; 13.11.2010
comment
@Tyler: перечисления C # - это просто имена, связанные с числами, без автоматической проверки или чего-либо. Перечисления IMO - это единственный бит Java, который на самом деле лучше, чем C #. - person Jon Skeet; 13.11.2010
comment
Живанезе объясняет истинную причину, это языковое ограничение. Надуманное объяснение хорошо известного набора значений - ерунда, это допустимый вариант использования. - person npiv; 29.11.2011
comment
@npiv: Хотеть иметь возможность это действительно так, но это противоречит другим частям работы перечислений. В основном здесь есть противоречивые требования. - person Jon Skeet; 29.11.2011
comment
Джон, я не уверен, что понимаю, почему это нельзя было поддержать. Что не так со сценарием, в котором генерируются два класса: A расширяет Enum, а B расширяет A (где d является экземпляром B)? В этом случае экземпляры A могут быть переданы коду, который использует B, когда это необходимо, но значения типа B должны быть явно приведены, если переданы коду, который был написан для A (что вызовет исключение ClassCastException в случае, если значение was 'd'), что, кажется, удовлетворяет всем необходимым ограничениям ... - person Jules; 27.02.2012
comment
@Jules: Если B расширяет A, то наверняка любое значение B должно быть совместимо с A по обычным правилам Java. Изменение этого было бы очень запутанным, ИМО. - person Jon Skeet; 27.02.2012
comment
@JonSkeet Может быть, поможет другое ключевое слово / концепция (включает?), Потому что на самом деле вы создаете новое перечисление вместо subEnum. Например: enum StringMethodName includes enum ObjectMethodName. Тогда я бы счел логичным, что StringMethodName.clone выдает ошибку компиляции, когда ожидается ObjectMethodName, и что ObjectMethodName.clone компилируется нормально, когда ожидается StringMethodName. - person Briguy37; 16.03.2012
comment
Не согласен с @JonSkeet здесь. В моем случае использования я хотел бы отделить всю неприятную логику в моем большом перечислении и скрыть эту логику, а также определить чистое перечисление, которое расширяет другое, которое скрыто. Перечисления с большим количеством логики превосходят идею объявления чистых переменных, поэтому вам не нужно объявлять сотни статических строковых переменных, поэтому класс с 5 перечислениями не становится нечитаемым и слишком большим в строках. Я не хочу, чтобы другие разработчики беспокоились о копировании и вставке этого фрагмента кода для следующего проекта, а вместо этого расширяли base_enum ... для меня это имеет смысл ... - person mmm; 11.04.2012
comment
@Hamidam: Зачем чистому перечислению нужно расширять другое? Почему он не мог просто использовать его для внутренних целей? Извините, из вашего комментария не совсем понятно, почему вы считаете, что здесь желательно наследование. - person Jon Skeet; 11.04.2012
comment
@JonSkeet Вот пример использования: создайте класс AuditFields, расширяемый большинством сущностей db (DRY). Одно поле аудита - update_action varchar (60). Использование строкового перечисления в стандарте поля делает его полезным. Но для поля будет много перечислений, слишком много, чтобы сделать его поддерживаемым или разумным для программистов, использующих его. Решение: создать enum super и расширить его (не может). Используйте super, чтобы определить поле объекта. Расширьте функциональную область суперразлома по приложениям, уменьшив количество значений для перечисления и сделав ее полезной. Только теперь это статические поля интерфейса старой школы, которые не заставляют их использовать. - person Bill Rosmus; 31.08.2012
comment
@givanse ... не соглашаясь с вами в отношении неявного расширения java.lang.Enum, являющегося причиной ненаследования, поскольку каждый класс в java также неявно наследует класс Object, но он может наследовать какой-то другой класс, поскольку он тогда придет в иерархию как Object->A->B вместо Object->A->B extends Object - person mickeymoon; 28.04.2013
comment
Полностью не согласен с @givanse и @npiv здесь: это не имеет ничего общего с множественным наследованием, как правильно объясняет @mickeymoon. Настоящая причина в том, что extends является отношением is-a, и поэтому подкласс может использоваться везде, где требуется его суперкласс, но (используя пример OP) d не будет допустимым значением для типа A, и поэтому не может быть передан где Требуется А. (Кстати, единственный способ расширения перечисления может концептуально вписаться в модель расширений Java - это если подперечисления допускают только подмножество своего суперперечисления; подумайте о теории множеств в математике.) - person daiscog; 22.01.2015
comment
Старый ответ, но ... @daiscog Oracle согласен с объяснением множественного наследования: Примечание. Все перечисления неявно расширяют java.lang.Enum. Поскольку класс может расширять только одного родителя (см. Объявление классов), язык Java не поддерживает множественное наследование состояния (см. Множественное наследование состояния, реализации и типа), и поэтому перечисление не может расширять что-либо еще. Из docs.oracle.com/javase/tutorial/java/javaOO/enum .html. Mickeymoon, все классы неявно расширяют Object - если они явно не расширяют что-то еще. - person nasch; 26.07.2016
comment
@nasch Я думаю, что Oracle имеет в виду расширение родительского класса, отличного от перечисления, с помощью базового класса перечисления. В вопросе плакат хочет расширить другое перечисление, которое само уже расширяет java.lang.Enum, поэтому это не множественное наследование. - person daiscog; 26.07.2016
comment
@daiscog Вы не можете расширить перечисление другим перечислением, как обсуждалось, как и вы не можете расширить одно с помощью класса. Согласно моей IDE, это потому, что перечисление неявно является окончательным, но я считаю, что если вы попробуете, вы столкнетесь с ошибкой. - person nasch; 26.07.2016
comment
@nasch Да, именно так. Причина, по которой вы не можете расширить перечисление другим перечислением, заключается в том, что перечисления неявно являются окончательными; что не имеет ничего общего с множественным наследованием. - person daiscog; 26.07.2016
comment
@daiscog Почему нельзя и то, и другое? Неявное окончательное расширение не помешает перечислению расширять класс, но неявное расширение Enum делает. - person nasch; 26.07.2016
comment
@nasch Вопрос не в расширении класса, а в расширении другого перечисления. Да, если бы перечисления не были неявно окончательными, то отсутствие поддержки множественного наследования помешало бы им расширить класс, не являющийся перечислением. Но вопрос был в том, почему одно перечисление не может расширять другое перечисление. Ответ: потому что перечисления окончательны. - person daiscog; 26.07.2016
comment
@daiscog Если вопрос в том, почему перечисление не может расширять другое перечисление, то, поскольку оно уже неявно расширяет Enum, также является правильным и достаточным ответом. Ни то, ни другое не так. Для предотвращения этого достаточно одного факта, поэтому вы не можете сделать это по обеим причинам. - person nasch; 26.07.2016
comment
Потому что B.a, B.b и B.c имели бы разные функции, установленные на B.d, если бы это было возможно .. Так как B.a не относится к классу B, а к классу A. B мог добавить дополнительные методы. - person WORMSS; 31.10.2017

Перечисления представляют собой полное перечисление возможных значений. Так что (бесполезный) ответ - нет.

В качестве примера реальной проблемы возьмем рабочие дни, выходные дни и, в совокупности, дни недели. Мы могли бы определить все дни в пределах дней недели, но тогда мы не смогли бы представить свойства, относящиеся ни к будним, ни к выходным дням.

Что мы могли бы сделать, так это иметь три типа перечислений с отображением между рабочими днями / выходными днями и днями недели.

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

В качестве альтернативы мы могли бы иметь открытый интерфейс для дня недели:

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

Или мы могли бы объединить два подхода:

interface Day {
    ...
}
public enum Weekday implements Day {
    MON, TUE, WED, THU, FRI;
    public DayOfWeek toDayOfWeek() { ... }
}
public enum WeekendDay implements Day {
    SAT, SUN;
    public DayOfWeek toDayOfWeek() { ... }
}
public enum DayOfWeek {
    MON, TUE, WED, THU, FRI, SAT, SUN;
    public Day toDay() { ... }
}
person Tom Hawtin - tackline    schedule 12.09.2009
comment
Нет ли с этим проблем? Оператор switch не будет работать с интерфейсом, но он работает с обычным перечислением. Отсутствие работы с переключателем убивает одну из приятных вещей в перечислениях. - person Manius; 29.07.2011
comment
@Crusader При втором подходе да, вам придется использовать цепочку _1 _-_ 2_ или что-то в этом роде. При первом подходе интерфейса нет. С третьим вам нужно будет позвонить toDayOfWeek, если у вас есть Day ссылка. - person Tom Hawtin - tackline; 29.07.2011
comment
Думаю, с этим может быть другая проблема. Нет равенства между Weekday.MON и DayOfWeek.MON. Разве это не еще одно большое преимущество перечислений? У меня нет лучшего решения, я просто понимаю это, пытаясь найти лучший ответ. Отсутствие возможности использовать == немного напрягает руку. - person Snekse; 22.11.2011
comment
@Snekse Определенно существует проблема, которую компилятор не будет (в настоящее время не смотреть - я думаю, ее можно было бы добавить), если вы случайно сравните Weekday с DayOfWeek.MON или аналогичным, используя Object.equals, Map.get, List.indexOf и т. Д. (См. smallwig.blogspot.co.uk/2007 / 12 /) - person Tom Hawtin - tackline; 30.03.2012
comment
@Crusader да, это именно компромисс. Если вам нужно что-то расширяемое, у вас не может быть фиксированных операторов переключения, если вам нужен набор фиксированных известных значений, у вас тавтологически не может быть чего-то расширяемого. - person djechlin; 21.02.2013
comment
Переходя от перечисления к интерфейсу, вы также теряете статический вызов values ​​(). Это затрудняет рефакторинг, особенно если вы решите расширить свое перечисление и добавить интерфейс в качестве барьера абстракции к установленному перечислению. - person Joshua Goldberg; 01.05.2013
comment
у вас перевалили по тарелочкам !! - person Jatin Sehgal; 29.06.2015
comment
Этот подход получения перечисления из интерфейса используется API Java 1.7, например. java.nio.file.Files.write () принимает массив OpenOption в качестве последнего аргумента. OpenOption - это интерфейс, но когда мы вызываем эту функцию, мы обычно передаем константу перечисления StandardOpenOption, производную от OpenOption. Это имеет то преимущество, что его можно расширять, но есть и недостатки. Реализация страдает от того факта, что OpenOption - это интерфейс. Он создает HashSet ‹OpenOption› из переданного массива, тогда как он мог бы создать EnumSet, более эффективно занимающий пространство и время. И он не может использовать переключатель. - person Klitos Kyriacou; 06.11.2015

Рекомендуемое решение - расширяемый шаблон перечисления.

Это включает в себя создание интерфейса и его использование там, где вы в настоящее время используете перечисление. Затем заставьте перечисление реализовать интерфейс. Вы можете добавить больше констант, сделав это новое перечисление также расширением интерфейса.

person JodaStephen    schedule 12.09.2009
comment
Стоит упомянуть об их использовании фабричного метода в интерфейсе. Отличный способ поделиться общей функциональностью между связанными Enums, учитывая, что расширение не является жизнеспособным решением. - person Tim Clemons; 24.05.2016
comment
Не могли бы вы предоставить более подробную информацию (код :)) об этом шаблоне? - person Dherik; 25.08.2016
comment
Этот шаблон не позволяет расширять значения перечисления. В чем суть заданного вопроса. - person Eria; 18.09.2018

Под обложками ваш ENUM - это просто обычный класс, сгенерированный компилятором. Этот сгенерированный класс расширяет java.lang.Enum. Техническая причина, по которой вы не можете расширить сгенерированный класс, заключается в том, что сгенерированный класс final. Концептуальные причины его окончательности обсуждаются в этой теме. Но добавлю в обсуждение механику.

Вот тестовое перечисление:

public enum TEST {  
    ONE, TWO, THREE;
}

Полученный код из javap:

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

Возможно, вы могли бы ввести этот класс самостоятельно и отбросить «финал». Но компилятор не позволяет вам напрямую расширять java.lang.Enum. Вы можете решить НЕ расширять java.lang.Enum, но тогда ваш класс и его производные классы не будут instanceof java.lang.Enum ... что может не иметь для вас никакого значения!

person ChrisCantrell    schedule 17.11.2012
comment
Что делает пустой статический блок? 'static {};' - person soote; 21.03.2016
comment
В нем нет кода. Программа javap показывает пустой блок. - person ChrisCantrell; 22.03.2016
comment
Странно иметь его там, если он ничего не делает, не так ли? - person soote; 22.03.2016
comment
Ты прав! Виноват. Это НЕ пустой блок кода. Если вы запустите javap -c, вы увидите фактический код внутри статического блока. Статический блок создает все экземпляры ENUM (здесь ОДИН, ДВА и ТРИ). Прости за это. - person ChrisCantrell; 23.03.2016
comment
Просто ради удовольствия я попробовал набрать класс, полученный вами от javap, напрямую, чтобы посмотреть, смогу ли я отбросить окончательный вариант. Никаких кубиков. Компилятор не позволит мне явно расширить Enum. (Неважно, вызываю я свой класс final или нет - ошибка возникает из-за попытки расширить Enum.) - person Brick; 23.02.2017
comment
Спасибо за прямой факт: потому что java.lang.Enum объявлен окончательным. - person Benjamin; 03.06.2017
comment
@Brick: это можно сделать, если вы используете старый компилятор, который не знает правила, запрещающего расширение Enum вручную. - person Holger; 14.06.2019

enum A {a,b,c}
enum B extends A {d}
/*B is {a,b,c,d}*/

можно записать как:

public enum All {
    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}

Чем это может быть полезно. Допустим, нам нужно что-то вроде: у нас есть события, и мы используем перечисления. Эти перечисления можно сгруппировать с помощью аналогичной обработки. Если у нас есть операция со многими элементами, то некоторые события запускают операцию, некоторые - просто шаг, а другие завершают операцию. Чтобы собрать такую ​​операцию и избежать длинного случая переключения, мы можем сгруппировать их, как в примере, и использовать:

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

Пример:

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

private AtmOperationStatus(StatusGroupInterface ... pList){
    for (StatusGroupInterface group : pList){
        group.addMember(this);
    }
}
public boolean is(StatusGroupInterface with){
    for (AtmOperationStatus eT : with.getMembers()){
        if( eT .equals(this))   return true;
    }
    return false;
}
// Each group must implement this interface
private interface StatusGroupInterface{
    EnumSet<AtmOperationStatus> getMembers();
    void addMember(AtmOperationStatus pE);
}
// DEFINING GROUPS
public enum State_StatusGroup implements StatusGroupInterface{
    START, STEP, FAIL, FINISH;

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

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

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

Добавьте еще несколько продвинутых:

public enum AtmEventType {

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

Выше, если у нас есть какой-то сбой (myEvent.is (State_StatusGroup.FAIL)), то, повторяя предыдущие события, мы можем легко проверить, должны ли мы отменить денежный перевод:

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

Это может быть полезно для:

  1. включая подробные метаданные о логике обработки, меньше запоминать
  2. реализация некоторого множественного наследования
  3. мы не хотим использовать структуры классов, например. для отправки коротких статусных сообщений
person Waldemar Wosiński    schedule 06.06.2012
comment
Это отличное решение проблемы. - person HopefullyHelpful; 01.08.2020

Если вы его пропустили, есть глава в превосходной книге Джошуа Блоха Эффективная Java, 2-е издание.

  • Глава 6 - Перечисления и аннотации
  • Правило 34: Эмуляция расширяемых перечислений с помощью интерфейсов

Просто вывод:

Незначительный недостаток использования интерфейсов для имитации расширяемых перечислений заключается в том, что реализации не могут быть унаследованы от одного типа перечисления к другому. В случае нашего примера Operation, логика для хранения и извлечения символа, связанного с операцией, дублируется в BasicOperation и ExtendedOperation. В этом случае это не имеет значения, потому что дублируется очень мало кода. Если бы было больше общих функций, вы могли бы инкапсулировать их во вспомогательный класс или статический вспомогательный метод, чтобы исключить дублирование кода.

Таким образом, хотя вы не можете написать расширяемый тип перечисления, вы можете эмулировать его, написав интерфейс, который будет работать с базовым типом перечисления, который реализует интерфейс. Это позволяет клиентам писать свои собственные перечисления, реализующие интерфейс. Затем эти перечисления можно использовать везде, где можно использовать базовый тип перечисления, при условии, что API написаны в терминах интерфейса.

person Guillaume Husta    schedule 22.07.2014
comment
Для тех, кто использует обновленную версию Effective Java, третье издание: тот же шаблон обсуждается в главе 6, пункт 38 (стр. 176). Названия глав и предметов не изменились. - person Joel; 24.06.2020

Вот способ, которым я нашел, как расширить перечисление в другое перечисление, это очень простой подход:

Допустим, у вас есть перечисление с общими константами:

public interface ICommonInterface {

    String getName();

}


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

    private final String name;

    EnumCriteriaComun(String name) {
        name= name;
    }

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

тогда вы можете попробовать сделать расширение руководства следующим образом:

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

    private final String name;

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

    EnumCriteriaComun(String name) {
        name= name;
    }

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

конечно, каждый раз, когда вам нужно расширить константу, вы должны изменить свои файлы SubEnum.

person Juan Pablo G    schedule 27.12.2014
comment
интересно, мы могли бы также использовать само перечисление toString (), а в конце сравнивать строки; и чтобы использовать переключатель, нам просто нужно было бы привести объект к известному перечислению; единственная проблема будет в том, что 2 разработчика расширят и создадут идентичный идентификатор перечисления, а затем попытаются объединить оба кода :), теперь я думаю, что понимаю, почему перечисление должно оставаться не расширяемым. - person Aquarius Power; 21.06.2016

Я стараюсь избегать перечислений, потому что они не расширяемы. Чтобы остаться с примером OP, если A находится в библиотеке, а B - в вашем собственном коде, вы не можете расширить A, если это перечисление. Вот как я иногда заменяю перечисления:

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

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

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

Есть некоторые ямы, которых следует избегать, см. Комментарии в коде. В зависимости от ваших потребностей это надежная расширяемая альтернатива перечислениям.

person sulai    schedule 09.09.2012
comment
это может быть хорошо, если вам просто нужен какой-то порядковый номер для экземпляров. Но перечисления также имеют свойство name, которое очень полезно. - person inor; 02.10.2018

Вот как я улучшаю шаблон наследования перечисления с помощью проверки времени выполнения в статическом инициализаторе. BaseKind#checkEnumExtender проверяет, что «расширяющееся» перечисление точно так же объявляет все значения базового перечисления, поэтому #name() и #ordinal() остаются полностью совместимыми.

Для объявления значений все еще используется копипаст, но программа быстро дает сбой, если кто-то добавил или изменил значение в базовом классе, не обновляя расширяющие.

Общее поведение для разных перечислений, расширяющих друг друга:

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

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

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

Базовое перечисление с методом проверки:

public enum BaseKind implements Kind {

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

  ;

  private final String description ;

  public String description() {
    return description ;
  }

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

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


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

}

Пример расширения:

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

  private final String description ;

  public String description() {
    return description ;
  }

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

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

}
person Laurent Caillette    schedule 18.05.2013

На основе ответа @Tom Hawtin - tackline мы добавляем поддержку коммутатора,

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

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

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

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

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

switch(wends.valueOf()){
    case SAT:
    case SUN:
}
person Khaled Lela    schedule 20.09.2016
comment
Какая польза от метода valueOf()? - person Axel Advento; 25.04.2017
comment
@AxelAdvento Идея здесь в том, что мы зависим от интерфейса Day, который имеет метод valueOf(), а затем switch(Day.valueOf()), он реализуется WeekDay, WeekEndDay перечислениями. - person Khaled Lela; 25.04.2017

Я предлагаю вам пойти по другому пути.

Вместо того, чтобы расширять существующее перечисление, создайте более крупное и создайте его подмножество. Например, если у вас есть перечисление под названием PET, и вы хотите расширить его до ANIMAL, вы должны сделать это вместо этого:

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

Будьте осторожны, домашние животные не являются неизменяемыми коллекциями, вы можете использовать Guava или Java9 для большей безопасности.

person Guillaume Robbe    schedule 28.07.2017

У меня была такая же проблема, и я хотел бы опубликовать свою точку зрения. Я думаю, что есть пара мотивирующих факторов, чтобы сделать что-то вроде этого:

  • Вы хотите иметь несколько связанных кодов перечисления, но в разных классах. В моем случае у меня был базовый класс с несколькими кодами, определенными в связанном перечислении. Позже (сегодня!) Я хотел предоставить базовому классу некоторые новые функции, что также означало новые коды для перечисления.
  • Производный класс будет поддерживать перечисление как базовых классов, так и собственное. Никаких повторяющихся значений перечисления! Итак: как получить перечисление для подкласса, которое включает перечисление его родителя вместе с его новыми значениями.

Использование интерфейса на самом деле не помогает: вы можете случайно получить повторяющиеся значения перечисления. Не желательно.

В итоге я просто объединил перечисления: это гарантирует, что не может быть никаких повторяющихся значений за счет меньшей привязки к соответствующему классу. Но я подумал, что проблема с дублированием была моей главной проблемой ...

person dsummersl    schedule 21.07.2011

Чтобы понять, почему расширение Enum нецелесообразно на уровне реализации языка, нужно учитывать, что произойдет, если вы передадите экземпляр расширенного Enum в подпрограмму, которая понимает только базовое Enum. Переключатель, который обещал компилятор, охватил все случаи, на самом деле не охватывал бы эти расширенные значения Enum.

Это дополнительно подчеркивает, что значения Java Enum не являются целыми числами, такими как, например, C: чтобы использовать Java Enum в качестве индекса массива, вы должны явно запросить его член ordinal (), чтобы дать java Enum произвольное целочисленное значение, которое вы должны добавить явное поле для этого и ссылка на указанный член.

Это не комментарий к желанию OP, а просто к тому, почему Java никогда этого не сделает.

person user2543191    schedule 05.05.2017

В надежде, что это элегантное решение моего коллеги даже будет замечено в этом длинном посте, я хотел бы поделиться этим подходом к созданию подклассов, который следует за интерфейсным подходом и за его пределами.

Имейте в виду, что здесь мы используем настраиваемые исключения, и этот код не будет компилироваться, если вы не замените его своими исключениями.

Документация обширна, и я надеюсь, что она понятна для большинства из вас.

Интерфейс, который необходимо реализовать в каждом подклассе перечисления.

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

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

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

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

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

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

Реализующий базовый класс ENUM.

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

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

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

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

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

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

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

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

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

Подкласс ENUM, который «наследует» от базового класса.

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

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

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

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

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

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

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

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

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

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

Наконец, общий ParameterImpl для добавления некоторых утилит.

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

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

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

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

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

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

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

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

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

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

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

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

    return this.parseOther(valueStr);
  }

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

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

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

    return this.toStringOther(value);
  }

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

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

Мой способ кодирования был бы следующим:

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

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

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:
            throw new IllegalStateException("This should never happen.");
    }
}

Код можно записать так:

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

Начиная с Java 7, вы можете делать то же самое с String:

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

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

Используя замену перечисления:

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