Gli enum possono essere suddivisi in sottoclassi per aggiungere nuovi elementi?


535

Voglio prendere un enum esistente e aggiungere più elementi ad esso come segue:

enum A {a,b,c}

enum B extends A {d}

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

Questo è possibile in Java?


12
Un motivo per farlo è per verificare la situazione in cui è presente un valore enum non valido senza introdurre un valore enum non valido nella sorgente principale.
Archimede Trajano,

Sì, un esempio di purezza "linguistica". Penso che ciò che si desidera sia per l'idea del "lavoro di contabilità" per il risparmio di lavoro di un insieme di interi con incremento automatico come uno ha in C ++ in modo da poter iniziare un nuovo set come estensione del vecchio set a partire da 1+ l'ultimo valore del set precedente e se le voci sono denominate ereditare i nomi dal "sottoinsieme comune". Sebbene l'enumerazione java abbia alcune cose interessanti, manca il semplice intero automatizzato a incremento automatico che dichiara l'aiuto fornito dall'enum C ++.
Peter

4
In realtà, quando estendi la tua enum con nuovi valori, stai creando non una sottoclasse, ma una superclasse. Puoi usare i valori dell'enum di base ovunque invece dell'enum "esteso", ma non viceversa, quindi secondo il principio di sostituzione di Liskov, l'enum esteso è una superclasse di enum di base.
Ilya,

@Ilya ... sì, è vero. Sottolineo che la domanda ha determinati casi d'uso nel mondo reale. Per amor di discussione, prendere in considerazione una base di Enum di: PrimaryColours; è ragionevole voler super- -class questo per Enum PrimaryAndPastelColoursaggiungendo nuovi nomi di colori. Liskov è ancora l'elefante nella stanza. Quindi perché non iniziare con un Enum di base di: AllMyColours- E poi si potrebbe sottoclassare tutti i colori in: PrimaryAndPastelColourse successivamente sottoclassarlo in: PrimaryColours(tenendo presente la gerarchia). Tuttavia, Java non lo permetterà.
sarà il

Risposte:


451

No, non puoi farlo in Java. A parte qualsiasi altra cosa, dsarebbe presumibilmente un esempio di A(data la normale idea di "estensioni"), ma gli utenti che solo sapevano Anon lo saprebbero - il che sconfigge il punto in cui un enum è un set ben noto di valori.

Se potessi dirci di più su come vuoi utilizzarlo , potremmo potenzialmente suggerire soluzioni alternative.


516
Tutti gli enum implicitamente estendono java.lang.Enum. Poiché Java non supporta l'ereditarietà multipla, un enum non può estendere nient'altro.
givanse,

9
Il motivo che voglio estendere è perché mi piacerebbe avere una classe base chiamata ad esempio IntEnum, che assomigli a questo: stackoverflow.com/questions/1681976/enum-with-int-value-in-java/… . Quindi tutti i miei enum potrebbero estenderlo ... in questo caso solo beneficiando dell'eredità e quindi non dovrei duplicare frequentemente questo codice "enum basato su int". Sono nuovo di Java e vengo da C #, e spero che mi manchi qualcosa. La mia opinione attuale è che gli enumerati Java sono un dolore rispetto a C #.
Tyler Collier,

30
@Tyler: gli enumerati C # sono solo nomi associati ai numeri, senza convalida automatica o altro . Gli enumerati IMO sono l'unico bit di Java che in realtà è migliore di C #.
Jon Skeet,

21
Non sono d'accordo con @JonSkeet qui. Nel mio caso d'uso, vorrei separare tutta la cattiva logica nel mio grande enum, avere la logica nascosta e definire un enum pulito che estende l'altro nascosto. Gli enum con molta logica battono l'idea di avere dichiarate variabili pulite, quindi non è necessario dichiarare centinaia di variabili di stringhe statiche in modo che una classe con 5 enum non diventi illeggibile e troppo grande nelle linee. Non voglio che gli altri sviluppatori si preoccupino di copiare e incollare quella pace di codice per il prossimo progetto e di estendere invece base_enum ... ha senso per me ...
mmm

43
@givanse ... non concordare con te sul punto di estensione implicita di java.lang.Enum essendo la causa della non ereditarietà poiché ogni classe in java eredita implicitamente anche la classe Object, ma può ereditare un'altra classe come sarebbe allora nella gerarchia come Object->A->Binvece diObject->A->B extends Object
mickeymoon il

317

Gli enumerati rappresentano un elenco completo di possibili valori. Quindi la risposta (inutile) è no.

Ad esempio di un problema reale, prendere giorni della settimana, giorni del fine settimana e, l'unione, giorni della settimana. Potremmo definire tutti i giorni entro i giorni della settimana, ma non saremmo in grado di rappresentare proprietà speciali per i giorni feriali e i giorni del fine settimana.

Quello che potremmo fare è avere tre tipi di enum con una mappatura tra giorni feriali / giorni feriali e giorni della settimana.

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

In alternativa, potremmo avere un'interfaccia aperta per il giorno della settimana:

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

Oppure potremmo combinare i due approcci:

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
Non c'è un problema con questo? Un'istruzione switch non funziona su un'interfaccia, ma funziona su una normale enum. Non funziona con il tipo di interruttore che uccide una delle cose più belle degli enum.
Manius,

9
Sto pensando che potrebbe esserci un altro problema con questo. Non esiste uguaglianza tra Weekday.MON e DayOfWeek.MON. Non è l'altro grande vantaggio delle enumerazioni? Non ho una soluzione migliore, sto solo realizzando questo mentre sto cercando di trovare la risposta migliore. La mancanza di poter usare == forza un po 'la mano.
Snekse,

2
@Crusader sì, è proprio questo il compromesso. Se vuoi qualcosa di espandibile non puoi avere istruzioni switch fisse, se vuoi un insieme di valori noti fissi non puoi tautologicamente avere qualcosa di espandibile.
Djechlin,

3
Passando da enum a interfaccia, perdi anche la chiamata statica a valori (). Questo rende difficile il refactoring, specialmente se decidi di estendere il tuo enum e aggiungere l'interfaccia come barriera di astrazione a un enum stabilito.
Joshua Goldberg,

4
Questo approccio di derivazione di un enum da un'interfaccia viene utilizzato dall'API Java 1.7, ad esempio java.nio.file.Files.write () utilizza una matrice di OpenOption come ultimo argomento. OpenOption è un'interfaccia, ma quando chiamiamo questa funzione di solito passiamo una costante enum StandardOpenOption, che deriva da OpenOption. Questo ha il vantaggio di essere estensibile, ma presenta anche degli svantaggi. L'implementazione risente del fatto che OpenOption è un'interfaccia. Crea un HashSet <OpenOption> dall'array passato, quando avrebbe potuto creare un EnumSet più efficiente in termini di spazio e tempo. E non può usare switch.
Klitos Kyriacou,

71

La soluzione consigliata a questo è il modello enum estensibile .

Ciò comporta la creazione di un'interfaccia e l'utilizzo di quella in cui si utilizza attualmente l'enum. Quindi fai in modo che enum attui l'interfaccia. Puoi aggiungere più costanti facendo sì che il nuovo enum estenda anche l'interfaccia.


Vale la pena chiamare il loro uso di un metodo di fabbrica nell'interfaccia. Ottimo modo per condividere funzionalità comuni tra Enum correlati dato che l'estensione non è una soluzione praticabile.
Tim Clemons,

8
Potete fornire maggiori dettagli (codice :)) su questo modello?
Dherik,

3
Questo modello non consente di estendere i valori di un enum. Qual è il punto nella domanda posta.
Eria,

55

Sotto le coperte il tuo ENUM è solo una classe normale generata dal compilatore. La classe generata si estende java.lang.Enum. Il motivo tecnico per cui non è possibile estendere la classe generata è che la classe generata è final. Le ragioni concettuali per la sua conclusione sono discusse in questo argomento. Ma aggiungerò la meccanica alla discussione.

Ecco un enum di prova:

public enum TEST {  
    ONE, TWO, THREE;
}

Il codice risultante da 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);
}

Probabilmente potresti scrivere questa classe da solo e rilasciare il "finale". Ma il compilatore ti impedisce di estendere direttamente "java.lang.Enum". Potresti decidere di NON estendere java.lang.Enum, ma poi la tua classe e le sue classi derivate non sarebbero un'istanza di java.lang.Enum ... che potrebbe non importarti davvero!


1
Cosa sta facendo il blocco statico vuoto? 'statico {};'
Soote,

1
Non contiene codice. Il programma "javap" mostra il blocco vuoto.
ChrisCantrell,

Strano averlo lì se non sta facendo niente non è vero?
Soote,

4
Hai ragione! Errore mio. NON è un blocco di codice vuoto. Se esegui "javap -c" vedrai il codice effettivo all'interno del blocco statico. Il blocco statico crea tutte le istanze ENUM (UNA, DUE e TRE qui). Mi dispiace per quello.
ChrisCantrell,

1
Grazie per aver affermato il fatto chiaro: perché java.lang.Enum è dichiarato definitivo.
Benjamin,

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

può essere scritto come:

public enum All {
    a       (ClassGroup.A,ClassGroup.B),
    b       (ClassGroup.A,ClassGroup.B),
    c       (ClassGroup.A,ClassGroup.B),
    d       (ClassGroup.B) 
...
  • ClassGroup.B.getMembers () contiene {a, b, c, d}

Come può essere utile: diciamo che vogliamo qualcosa di simile: abbiamo eventi e stiamo usando enum. Tali enumerazioni possono essere raggruppate mediante elaborazioni simili. Se abbiamo un'operazione con molti elementi, alcuni eventi iniziano a funzionare, alcuni sono solo step e altri terminano l'operazione. Per raccogliere tale operazione ed evitare una lunga commutazione, possiamo raggrupparli come nell'esempio e usare:

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

Esempio:

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      
}  

Aggiungi alcuni più avanzati:

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),
(...)

In precedenza, se si verificano errori (myEvent.is (State_StatusGroup.FAIL)), durante l'iterazione di eventi precedenti, è possibile verificare facilmente se è necessario ripristinare il trasferimento di denaro tramite:

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

Può essere utile per:

  1. compresi i metadati espliciti sulla logica di elaborazione, meno da ricordare
  2. implementando parte dell'ereditarietà multipla
  3. non vogliamo usare le strutture di classe, ad es. per l'invio di brevi messaggi di stato

13

Ecco un modo in cui ho scoperto come estendere un enum ad un altro enum, è un approccio molto diretto:

Suposse hai un enum con costanti comuni:

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

quindi puoi provare a fare un manuale in questo modo:

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

ovviamente ogni volta che è necessario estendere una costante, è necessario modificare i file SubEnum.


interessante, potremmo usare anche l'enum toString (), e alla fine confrontare le stringhe; e per usare switch, dovremmo semplicemente lanciare l'oggetto su un enum noto; l'unico problema sarebbe che 2 sviluppatori si estendessero e creassero un ID enum identico, e in seguito cercassero di unire entrambi i codici :), ora penso di capire perché enum dovrebbe rimanere non estendibile.
Aquarius Power il

11

Nel caso lo avessi perso, c'è un capitolo nell'eccellente libro di Joshua Bloch " Java Effective, 2nd edition ".

  • Capitolo 6 - Enumerazioni e annotazioni
    • Articolo 34: Emula enum estensibili con interfacce

Estrai qui .

Solo la conclusione:

Uno svantaggio minore dell'uso di interfacce per emulare enum estensibili è che le implementazioni non possono essere ereditate da un tipo di enum a un altro. Nel caso del nostro esempio Operazione, la logica per memorizzare e recuperare il simbolo associato a un'operazione è duplicata in BasicOperation ed ExtendedOperation. In questo caso non importa perché viene duplicato pochissimo codice. Se fosse presente una quantità maggiore di funzionalità condivisa, è possibile incapsularla in una classe di supporto o in un metodo di supporto statico per eliminare la duplicazione del codice.

In sintesi, sebbene non sia possibile scrivere un tipo enum estensibile, è possibile emularlo scrivendo un'interfaccia per utilizzare un tipo enum di base che implementa l'interfaccia. Ciò consente ai client di scrivere i propri enum che implementano l'interfaccia. Questi enum possono quindi essere utilizzati ovunque sia possibile utilizzare il tipo di enum di base, supponendo che le API siano scritte in termini di interfaccia.


6

Tendo a evitare gli enum, perché non sono estensibili. Per rimanere con l'esempio dell'OP, se A è in una libreria e B nel tuo codice, non puoi estendere A se è un enum. Ecco come a volte sostituisco enums:

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

Ci sono alcuni punti da evitare, vedere i commenti nel codice. A seconda delle tue esigenze, questa è un'alternativa solida ed estensibile agli enum.


1
può andare bene se hai solo bisogno di un po 'ordinale per istanze. Ma enums ha anche una proprietà del nome che è piuttosto utile.
o

6

Questo è il modo in cui potrei migliorare il modello di ereditarietà enum con il controllo di runtime nell'inizializzatore statico. I BaseKind#checkEnumExtendercontrolli che "estendono" enum dichiarano tutti i valori dell'enum di base esattamente allo stesso modo #name()e #ordinal()rimangono pienamente compatibili.

È ancora coinvolto il copia-incolla per la dichiarazione dei valori, ma il programma fallisce rapidamente se qualcuno ha aggiunto o modificato un valore nella classe base senza aggiornare quelli estesi.

Comportamento comune per enumerazioni diverse che si estendono a vicenda:

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

Enum di base, con metodo di verifica:

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

}

Esempio di estensione:

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

Basato su @Tom Hawtin - risposta di risposta aggiungiamo il supporto switch,

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

A che serve il valueOf()metodo?
Axel Advento,

@AxelAdvento L'idea qui è che dipendiamo dall'interfaccia Dayche ha un metodo valueOf()quindi switch(Day.valueOf()), è implementato da WeekDay, WeekEndDayenums.
Khaled Lela,

3

Ti suggerisco di fare il contrario.

Invece di estendere l'enumerazione esistente, crearne una più grande e crearne un sottoinsieme. Ad esempio, se avevi un elenco chiamato PET e volessi estenderlo ad ANIMAL, dovresti invece farlo:

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

Fai attenzione, gli animali domestici non sono raccolte immutabili, potresti voler utilizzare Guava o Java9 per maggiore sicurezza.


2

Avendo avuto questo stesso problema, vorrei pubblicare la mia prospettiva. Penso che ci siano un paio di fattori motivanti per fare qualcosa del genere:

  • Volete avere alcuni codici enum correlati, ma in classi diverse. Nel mio caso avevo una classe base con diversi codici definiti in un enum associato. In un secondo momento (oggi!) Volevo fornire alcune nuove funzionalità alla classe base, il che significava anche nuovi codici per l'enum.
  • La classe derivata supporterebbe sia l'enumerazione delle classi base che la propria. Nessun valore enum duplicato! Quindi: come avere un enum per la sottoclasse che include l'enum del suo genitore insieme ai suoi nuovi valori.

L'uso di un'interfaccia non lo taglia davvero: puoi accidentalmente ottenere valori enum duplicati. Non desiderabile

Ho finito per combinare solo gli enum: questo assicura che non ci possano essere valori duplicati, a scapito di essere meno strettamente legato alla classe associata. Ma, ho pensato che il problema duplicato fosse la mia principale preoccupazione ...


2

Come aiuto per capire perché l'estensione di un Enum non è ragionevole a livello di implementazione del linguaggio considerare cosa accadrebbe se si passasse un'istanza dell'Enum esteso a una routine che comprende solo l'Enum di base. Uno switch che il compilatore ha promesso aveva coperto tutti i casi in realtà non coprirebbe quei valori Enum estesi.

Ciò sottolinea ulteriormente che i valori di Enum Java non sono numeri interi come quelli di C, ad esempio: per utilizzare un Enum Java come indice di array è necessario richiedere esplicitamente il suo membro ordinal (), per assegnare a un Enum java un valore intero arbitrario che è necessario aggiungere un campo esplicito per questo e fare riferimento a quel membro nominato.

Questo non è un commento sul desiderio dell'OP, solo sul motivo per cui Java non lo farà mai.


1

Nella speranza che questa elegante soluzione di un mio collega sia vista anche in questo lungo post, vorrei condividere questo approccio per la sottoclasse che segue l'approccio dell'interfaccia e oltre.

Tieni presente che qui utilizziamo le eccezioni personalizzate e questo codice non verrà compilato se non lo sostituisci con le tue eccezioni.

La documentazione è ampia e spero sia comprensibile per la maggior parte di voi.

L'interfaccia che ogni enumerazione di sottoclasse deve implementare.

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

La classe base ENUM di implementazione.

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

La sottoclasse ENUM che "eredita" dalla classe base.

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

Finalmente il generico ParameterImpl per aggiungere alcune utility.

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

Il mio modo di codificare sarebbe come segue:

// 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', '♯', '♭' }));
}

LinkedHashSetprevede sia che ciascuna voce esista una sola volta, sia che il suo ordine venga preservato. Se l'ordine non ha importanza, puoi HashSetinvece usarlo . Il seguente codice non è possibile in 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.");
    }
}

Il codice può essere scritto come segue:

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

Da Java 7 in poi puoi persino fare lo stesso con 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");
}

Usando la sostituzione 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.");
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.