Можно ли перечислять перечисления для добавления новых элементов? - PullRequest
484 голосов
/ 12 сентября 2009

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

enum A {a,b,c}

enum B extends A {d}

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

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

Ответы [ 15 ]

413 голосов
/ 12 сентября 2009

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

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

302 голосов
/ 12 сентября 2009

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

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

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

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() { ... }
}
67 голосов
/ 12 сентября 2009

Рекомендуемое решение для этого - расширяемый шаблон enum .

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

51 голосов
/ 17 ноября 2012

Под прикрытием 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, но тогда ваш класс и его производные классы не будут экземпляром java.lang.Enum ... который на самом деле не имеет для вас никакого значения!

26 голосов
/ 06 июня 2012
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. мы не хотим использовать структуры классов, напр. для отправки коротких сообщений о состоянии
12 голосов
/ 27 декабря 2014

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

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

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.

10 голосов
/ 22 июля 2014

Если вы пропустили это, в превосходной книге Джошуа Блоха есть глава " Java Effective, 2nd edition ".

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

Извлечение здесь .

Только вывод:

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

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

6 голосов
/ 18 мая 2013

Так я улучшаю шаблон наследования перечисления с помощью проверки во время выполнения в статическом инициализаторе. 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 ;
  }

}
6 голосов
/ 09 сентября 2012

Я стараюсь избегать перечислений, потому что они не расширяемы. Чтобы остаться на примере 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; };
}

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

3 голосов
/ 28 июля 2017

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...