Чи можна перерахувати перекласи для додавання нових елементів?


534

Я хочу взяти існуючий перелік і додати до нього більше елементів так:

enum A {a,b,c}

enum B extends A {d}

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

Чи можливо це на Java?


12
Причиною цього є тестування ситуації, коли існує недійсне значення перерахунку без введення недійсного значення перерахунку в основний джерело.
Архімед Траяно

Так, приклад "мовної" чистоти. Я думаю, що бажане - це ідея "економії праці" щодо "економії праці" набору автоматичного збільшення набору цілих чисел, таких як один у C ++, так що ви можете запустити новий набір як розширення старого набору, починаючи з 1+ останнього значення попереднього набору, і якщо записи названі, успадковуйте імена з "загальної підмножини". Незважаючи на те, що у переліку java enum є деякі приємні речі, йому не вистачає простого автоматизованого автоматичного збільшення цілого числа, що оголошує допомогу, яку надає перелік C ++.
peterk

4
Насправді, розширюючи перелік новими значеннями, ви створюєте не підклас, а надклас. Ви можете використовувати базові значення enum скрізь замість "розширеного" enum, але не навпаки, тому згідно з принципом заміщення Ліскова, розширений enum є надкласом базового enum.
Ілля

@Ilya ... так, це правда. Я зазначаю, що питання має певні реальні випадки використання. Заради аргументу, розглянемо базу Enum з: PrimaryColours; розумно хотіти супер -класу це Enum PrimaryAndPastelColours, додаючи нові назви кольорів. Лісков все ще слон у кімнаті. То чому б не почати з базового Enum з: AllMyColours- І тоді можна підкласифікувати всі кольори у: PrimaryAndPastelColoursа згодом підкласирувати це на: PrimaryColours(маючи на увазі ієрархію). Java також не дозволить цього.
буде

Відповіді:


450

Ні, ви не можете цього зробити на Java. Окрім нічого іншого, dтоді, мабуть, це буде екземпляр A(з огляду на звичайну ідею "розширюється"), але користувачі, які тільки знали про Aце, не знали б про це - що перемагає точку, коли ентус є відомим набором значення.

Якщо ви могли б розповісти більше про те, як ви хочете використовувати це, ми можемо запропонувати альтернативні рішення.


516
Усі енюми неявно поширюють 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 ... для мене це має сенс ...
mmm

43
@givanse ... не погоджуючись з вами з точки зору неявного розширення java.lang.Enum, що є причиною неспадкову спадщину, оскільки кожен клас у java також неявно успадковує клас Object, але він може успадковувати якийсь інший клас, як би він прийшов в ієрархію, Object->A->Bзамість цьогоObject->A->B extends Object
mickeymoon

317

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

Як приклад реальної проблеми візьміть будні, вихідні дні та, профспілки, дні тижня. Ми могли б визначити всі дні протягом днів тижня, але тоді ми не зможемо представити властивості, особливі ні для будних, ні для вихідних днів.

Ми могли б зробити три типи перерахунків з відображенням між буднями / вихідними днями та днями тижня.

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() { ... }
}

20
Чи немає з цим проблеми? Оператор перемикання не буде працювати в інтерфейсі, але він працює на регулярній перерахунку. Непрацюючи ж / перемикаючи, вбиває одну з приємніших речей щодо переліків.
Маній

9
Я думаю, що з цим може виникнути ще одна проблема. Немає рівності між будними днями.MON та DayOfWeek.MON. Чи не в цьому інша велика користь від перерахунків? Я не маю кращого рішення, просто усвідомлюючи це, намагаючись знайти найкращу відповідь. Відсутність можливості використовувати == трохи змушує руку.
Снексе

2
@Crusader так, це саме компроміс. Якщо ви хочете щось розгорнуте, у вас не може бути фіксованих операторів комутації, якщо ви хочете набір фіксованих відомих значень, у вас тавтологічно не може бути щось розгорнуте.
djechlin

3
Переходячи від перерахунку до інтерфейсу, ви також втрачаєте статичний виклик значень (). Це робить рефакторинг важким, особливо якщо ви вирішили розширити перерахунок і додати інтерфейс як бар'єр абстракції до встановленої перерахунку.
Джошуа Голдберг

4
Цей підхід до отримання перерахунку з інтерфейсу використовується Java 1.7 API, наприклад, java.nio.file.Files.write () приймає масив OpenOption як останній аргумент. OpenOption - це інтерфейс, але коли ми викликаємо цю функцію, ми зазвичай передаємо константу перерахунку StandardOpenOption, яка походить від OpenOption. Це має перевагу в тому, що він розширюється, але він має і недоліки. Реалізація страждає від того, що OpenOption є інтерфейсом. Він створює HashSet <OpenOption> з пройденого масиву, коли він міг би створити EnumSet, більш економічний за часом та часом. І він не може використовувати перемикач.
Klitos Kyriacou

71

Рекомендованим рішенням для цього є розширюваний візерунок перерахунку .

Це передбачає створення інтерфейсу та використання того, де ви зараз використовуєте enum. Потім змусити enum реалізувати інтерфейс. Ви можете додати більше констант, зробивши, що новий перелік також розширить інтерфейс.


Варто назвати їх використання заводського методу в інтерфейсі. Прекрасний спосіб ділитися загальною функціональністю між пов'язаними Enums, враховуючи, що розширення не є прийнятним рішенням.
Тім Клемонс

8
Чи можете ви надати більш детальну інформацію (код :)) про цю схему?
Дерік

3
Ця закономірність не дозволяє розширити значення перерахунків. Який сенс у заданому питанні.
Ерія

55

Під обкладинками ваш 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 ... що може насправді не мати значення для вас!


1
Що робить порожній статичний блок? 'статичний {};'
заспокоєння

1
У ньому немає коду. Програма "javap" показує порожній блок.
ChrisCantrell

Дивно, якщо він там є, якщо він нічого не робить, чи не так?
саж

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}*/

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

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. ми не хочемо використовувати структури класів, напр. для надсилання коротких повідомлень про стан

13

Ось спосіб, як я виявив, як поширити перерахунок на іншу, - це дуже прямий підхід:

Якщо ви вважаєте, що у вас є перелік із загальними константами:

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.


що цікаво, ми могли б використати також дуже enum toString (), а наприкінці порівняти рядки; і щоб використовувати перемикач, нам просто доведеться кинути об'єкт на відомий перелік; єдиною проблемою було б 2 розробники, що розширюють і створюють ідентичний ідентифікатор enum, а пізніше намагаються об'єднати обидва коди :), тепер я думаю, я розумію, чому enum повинен залишатися нерозширюваним.
Сила Водолія

11

Якщо ви пропустили це, є розділ чудової книги Джошуа Блоха " Java Effective, 2-е видання ".

  • Глава 6 - Перерахунки та анотації
    • Пункт 34: Емуляція розширюваних перерахунків за допомогою інтерфейсів

Витягніть тут .

Просто висновок:

Невеликим недоліком використання інтерфейсів для імітації розширюваних перерахунків є те, що реалізації не можуть бути успадковані від одного типу перерахунку до іншого. У випадку нашого прикладу "Операція" логіка зберігання та отримання символу, пов'язаного з операцією, дублюється у BasicOperation та ExtendedOperation. У цьому випадку це не має значення, оскільки дублюється дуже мало коду. Якщо існує більша кількість спільної функціональності, ви можете інкапсулювати її в клас помічників або статичний помічник, щоб усунути дублювання коду.

Підсумовуючи це, хоча ви не можете записати розширюваний тип перерахунку, ви можете емулювати його, написавши інтерфейс, щоб перейти з базовим типом перерахунку, який реалізує інтерфейс. Це дозволяє клієнтам писати власні переліки, які реалізують інтерфейс. Ці переліки можуть бути використані там, де може використовуватися базовий тип перерахунку, якщо припустимо, що API записані через інтерфейс.


6

Я схильний уникати переживань, оскільки вони не розширюються. Щоб залишитися на прикладі ОП, якщо 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; };
}

Є кілька ям, яких слід уникати, дивіться коментарі в коді. Залежно від ваших потреб, це солідна, розширювана альтернатива перелікам.


1
це може бути добре, якщо вам просто потрібен якийсь порядковий примірник. Але перерахунки також мають властивість імені, що є досить корисним.
inor

6

Ось так я покращую схему успадкування перерахунків за допомогою перевірки виконання в статичному ініціалізаторі. У 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 ;
  }

}

4

На основі @Tom Hawtin - відповіді про лінії, ми додаємо підтримку комутаторів,

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:
}

У чому полягає valueOf()метод?
Axel Advento

@AxelAdvento Ідея тут ми залежимо від інтерфейсу , Dayякий має метод , valueOf()то switch(Day.valueOf()), це реалізується WeekDay, WeekEndDayперерахувань.
Халед Лела

3

Я пропоную вам скористатися навпаки.

Замість розширення наявного перерахування створіть більший і створіть його підмножину. Для прикладу, якщо у вас було перерахування під назвою PET і ви хочете розширити його на ТВАРИНИ, слід зробити це замість цього:

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

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


2

Маючи цю саму проблему, я хотів би викласти свою точку зору. Я думаю, що є кілька мотивуючих факторів, щоб зробити щось подібне:

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

Використання інтерфейсу насправді не скорочує його: ви можете випадково отримати повторювані значення перерахунків. Не бажано.

У кінцевому підсумку я просто поєднав перерахунки: це гарантує, що не може бути жодних повторюваних значень, за рахунок того, щоб бути менш щільно прив'язаними до асоційованого класу. Але я вважав, що питання, що повторюється, було моїм головним питанням ...


2

Як допомога зрозуміти, чому розширення Enum не є розумним на рівні мовної реалізації, розглядати, що трапиться, якби ви передали екземпляр розширеного Enum в рутину, яка розуміє лише базовий Enum. Перемикач, який обіцяв компілятор, що охоплює всі випадки, насправді не покриє ці розширені значення Enum.

Це ще більше підкреслює, що значення Java Enum не є цілими числами, такими як C, наприклад, щоб використовувати Java Enum як індекс масиву, ви повинні явно попросити його порядкового () члена, щоб надати java Enum довільне ціле число, яке потрібно додати явне поле для цього та посилання, що назвав члена.

Це не коментування бажання ОП, а лише те, чому Java ніколи не збирається цього робити.


1

Сподіваємось, що це елегантне рішення мого колеги бачиться навіть у цьому довгому дописі, я б хотів поділитися таким підходом для підкласингу, який слід підходити до інтерфейсу і далі.

Зауважте, що ми тут використовуємо спеціальні винятки, і цей код не буде компілюватися, якщо ви не заміните його на винятки.

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

Інтерфейс, який потребує реалізація кожного підкласового переліку.

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);
  }
}

Нарешті, загальний параметрImpl, щоб додати деякі утиліти.

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());
  }
}

0

Мій спосіб кодування був би таким:

// 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");
}

Використання заміни enum:

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.");
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.