As enums podem ser subclassificadas para adicionar novos elementos?

534

Quero pegar uma enumeração existente e adicionar mais elementos a ela da seguinte maneira:

enum A {a,b,c}

enum B extends A {d}

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

Isso é possível em Java?

Mike
fonte
12
Um motivo para fazer isso é testar a situação em que há um valor de enum inválido sem introduzir um valor de enum inválido na fonte principal.
Archimedes Trajano
Sim, um exemplo de pureza "linguística". Eu acho que o que se deseja é a ideia de economia de mão-de-obra "bookeeping" de um conjunto de números inteiros com incremento automático, como um em C ++, para que você possa iniciar um novo conjunto como uma extensão do conjunto antigo, começando com 1+ o último valor do conjunto anterior e, se as entradas forem nomeadas, herdam os nomes do "subconjunto comum". Embora o java enum tenha algumas coisas interessantes, ele não possui a ajuda simples e simples de declaração automática de incremento automático automatizada que o C ++ enum fornece.
peterk
4
Na verdade, quando você estende sua enumeração com novos valores, está criando não uma subclasse, mas uma superclasse. Você pode usar valores de enum base em todos os lugares, em vez de enum "estendido", mas não vice-versa; portanto, de acordo com o Princípio de Substituição de Liskov, o enum estendido é uma superclasse de enum base.
Ilya
@ Ilya ... sim, isso é verdade. Ressalto que a questão tem casos de uso definidos no mundo real. Por causa do argumento, considere uma base de Enum de: PrimaryColours; é razoável querer Super de classe que isso Enum PrimaryAndPastelColourspela adição de novos nomes de cores. Liskov ainda é o elefante na sala. Então, por que não começar com um Enum base de: AllMyColours- E então pode-se subclassificar todas as cores para: PrimaryAndPastelColourse subclasses subseqüentemente isso para: PrimaryColours(mantendo a hierarquia em mente). O Java também não permitirá isso.
será

Respostas:

451

Não, você não pode fazer isso em Java. Além de qualquer outra coisa, dpresumivelmente seria uma instância de A(dada a idéia normal de "estende"), mas usuários que apenas conheciamA não sabiam disso - o que derrota o ponto de um enum ser um conjunto bem conhecido de valores.

Se você puder nos dizer mais sobre como deseja usar isso, poderíamos sugerir soluções alternativas.

Jon Skeet
fonte
516
Todas as enumerações estendem implicitamente java.lang.Enum. Como o Java não suporta herança múltipla, uma enum não pode estender mais nada.
givanse
9
A razão pela qual desejo estender é porque gostaria de ter uma classe base chamada, por exemplo, IntEnum, que se parece com isso: stackoverflow.com/questions/1681976/enum-with-int-value-in-java/… . Então todas as minhas enumerações poderiam estendê-la ... nesse caso, apenas se beneficiando da herança e, portanto, não precisaria duplicar esse código de "enumeração baseada em int" com freqüência. Eu sou novo em Java e sou de C #, e espero estar perdendo alguma coisa. Minha opinião atual é que enums Java são uma dor em comparação com C #.
Tyler Collier
30
@ Tyler: enums de C # são apenas nomes associados a números, sem validação automática ou algo assim . As enums da IMO são o bit de Java que é realmente melhor que o C #.
precisa
21
Não estou de acordo com @JonSkeet aqui. No meu caso de uso, gostaria de separar toda a lógica desagradável no meu grande enum, ocultar a lógica e definir uma enum limpa que estenda a outra oculta. Enums com muita lógica superam a idéia de declarar variáveis ​​limpas, assim você não precisa declarar centenas de variáveis ​​de strings estáticas para que uma classe com 5 enums não fique ilegível e muito grande nas linhas. Também não quero que os outros desenvolvedores se preocupem em copiar e colar essa paz de código para o próximo projeto e, em vez disso, estender o base_enum ... faz sentido para mim ...
mmm
43
@givanse ... não concordando com você no ponto de extensão implícita do java.lang.Enum, sendo a causa da não-herança, pois todas as classes em java também herdam implicitamente a classe Object, mas podem herdar alguma outra classe, pois ela viria na hierarquia como Object->A->Bem vez deObject->A->B extends Object
mickeymoon
317

Enums representam uma enumeração completa de valores possíveis. Portanto, a resposta (inútil) é não.

Como exemplo de um problema real, considere os dias da semana, finais de semana e, a união, os dias da semana. Poderíamos definir todos os dias dentro de dias da semana, mas não poderíamos representar propriedades especiais para dias da semana e fins de semana.

O que poderíamos fazer é ter três tipos de enumeração com um mapeamento entre dias da semana / dias da semana e dias da semana.

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

Como alternativa, poderíamos ter uma interface aberta para o dia da semana:

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

Ou podemos combinar as duas abordagens:

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() { ... }
}
Tom Hawtin - linha de orientação
fonte
20
Não há um problema com isso? Uma instrução switch não funciona em uma interface, mas funciona em uma enumeração regular. Não funcionar com o tipo de switch mata uma das coisas mais agradáveis ​​sobre enums.
Manius 29/07
9
Estou pensando que pode haver outro problema com isso. Não há igualdade entre o Weekday.MON e DayOfWeek.MON. Não é esse o outro grande benefício das enums? Não tenho uma solução melhor, apenas percebi isso enquanto tentava encontrar a melhor resposta. A falta de poder usar == força um pouco a mão.
Snekse
2
@ Crusader sim, essa é precisamente a troca. Se você deseja algo expansível, não pode ter instruções de chave fixas, se deseja um conjunto de valores conhecidos fixos, tautologicamente não pode ter algo expansível.
djechlin
3
Indo de enum para interface, você também perde a chamada estática para values ​​(). Isso dificulta a refatoração, especialmente se você decidir estender sua enumeração e adicionar a interface como uma barreira de abstração a uma enumeração estabelecida.
Joshua Goldberg
4
Essa abordagem de derivar uma enumeração de uma interface é usada pela API Java 1.7, por exemplo, java.nio.file.Files.write () pega uma matriz de OpenOption como o último argumento. OpenOption é uma interface, mas quando chamamos essa função geralmente passamos uma constante de enum StandardOpenOption, que é derivada de OpenOption. Isso tem a vantagem de ser extensível, mas também possui desvantagens. A implementação sofre com o fato de o OpenOption ser uma interface. Ele cria um HashSet <OpenOption> a partir da matriz passada, quando poderia ter criado um EnumSet com mais espaço e tempo. E não pode usar o interruptor.
Klitos Kyriacou 6/11
71

A solução recomendada para isso é o padrão de enum extensível .

Isso envolve criar uma interface e usá-la onde você usa a enumeração. Em seguida, faça o enum implementar a interface. Você pode adicionar mais constantes, fazendo com que a nova enum também estenda a interface.

JodaStephen
fonte
Vale a pena destacar o uso de um método de fábrica na interface. Ótima maneira de compartilhar funcionalidades comuns entre as Enums relacionadas, pois a extensão não é uma solução viável.
Tim Clemons
8
Você pode fornecer mais detalhes (código :)) sobre esse padrão?
Dherik
3
Esse padrão não permite estender os valores de uma enumeração. Qual é o ponto da pergunta.
Eria
55

Nos bastidores, seu ENUM é apenas uma classe regular gerada pelo compilador. Essa classe gerada se estende java.lang.Enum. A razão técnica pela qual você não pode estender a classe gerada é que a classe gerada éfinal . As razões conceituais para a sua finalização são discutidas neste tópico. Mas vou adicionar a mecânica à discussão.

Aqui está uma enumeração de teste:

public enum TEST {  
    ONE, TWO, THREE;
}

O código resultante do 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);
}

É possível que você possa digitar essa classe por conta própria e soltar a "final". Mas o compilador impede que você estenda "java.lang.Enum" diretamente. Você pode decidir NÃO estender o java.lang.Enum, mas sua classe e suas classes derivadas não seriam uma instância do java.lang.Enum ... o que pode não ser realmente importante para você!

ChrisCantrell
fonte
1
O que o bloco estático vazio está fazendo? 'static {};'
soote
1
Não possui código. O programa "javap" mostra o bloco vazio.
22416 ChrisCantrell
Estranho tê-lo lá, se não estiver fazendo nada, não é?
soote
4
Você está certo! Meu erro. NÃO é um bloco de código vazio. Se você executar "javap -c", verá o código real dentro do bloco estático. O bloco estático cria todas as instâncias ENUM (uma, duas e três aqui). Me desculpe por isso.
22416 ChrisCantrell
1
Obrigado por declarar o fato direto: porque java.lang.Enum é declarado final.
Benjamin
26
enum A {a,b,c}
enum B extends A {d}
/*B is {a,b,c,d}*/

pode ser escrito como:

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

Como pode ser útil: Digamos que queremos algo como: Temos eventos e estamos usando enumerações. Essas enumerações podem ser agrupadas por processamento semelhante. Se temos operação com muitos elementos, alguns eventos iniciam a operação, alguns são apenas um passo e outro finalizam a operação. Para reunir essa operação e evitar casos de comutação longos, podemos agrupá-los como no exemplo e usar:

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

Exemplo:

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      
}  

Adicione alguns mais avançados:

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

Acima, se tivermos alguma falha (myEvent.is (State_StatusGroup.FAIL)), iterando pelos eventos anteriores, podemos verificar facilmente se devemos reverter a transferência de dinheiro por:

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

Pode ser útil para:

  1. incluindo meta-dados explícitos sobre lógica de processamento, menos para lembrar
  2. implementando parte da herança múltipla
  3. não queremos usar estruturas de classe, ex. para enviar mensagens curtas de status
Waldemar Wosiński
fonte
13

Aqui está uma maneira de descobrir como estender uma enumeração para outra enumeração, é uma abordagem muito direta:

Suponha que você tenha um enum com constantes comuns:

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

então você pode tentar fazer um manual extendido desta maneira:

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

é claro que toda vez que você precisar estender uma constante, precisará modificar seus arquivos do SubEnum.

Juan Pablo G
fonte
interessante, poderíamos usar também o enum toString () e, no final, comparar strings; e para usar switch, teríamos apenas que converter o objeto em uma enumeração conhecida; o único problema seria 2 desenvolvedores estendendo e criando um id de enum idêntico e depois tentando mesclar os dois códigos :), agora acho que entendo por que o enum deve permanecer não extensível.
Poder de Aquário
11

Caso você tenha perdido, há um capítulo no excelente livro de Joshua Bloch " Java Effective, 2nd edition ".

  • Capítulo 6 - Enums e anotações
    • Item 34: Emule enums extensíveis com interfaces

Extraia aqui .

Apenas a conclusão:

Uma pequena desvantagem do uso de interfaces para emular enums extensíveis é que as implementações não podem ser herdadas de um tipo de enumeração para outro. No caso do nosso exemplo de Operação, a lógica para armazenar e recuperar o símbolo associado a uma operação é duplicada em BasicOperation e ExtendedOperation. Nesse caso, não importa, porque muito pouco código é duplicado. Se houvesse uma quantidade maior de funcionalidade compartilhada, você poderia encapsulá-la em uma classe auxiliar ou em um método auxiliar estático para eliminar a duplicação de código.

Em resumo, embora você não possa escrever um tipo de enum extensível, você pode emulá-lo escrevendo uma interface para usar um tipo de enum básico que implementa a interface. Isso permite que os clientes escrevam suas próprias enumerações que implementam a interface. Essas enumerações podem ser usadas sempre que o tipo de enumeração básico puder ser usado, assumindo que as APIs sejam gravadas em termos da interface.

Guillaume Husta
fonte
6

Eu tendem a evitar enums, porque eles não são extensíveis. Para ficar com o exemplo do OP, se A estiver em uma biblioteca e B em seu próprio código, você não poderá estender A se for um enum. É assim que às vezes substituo 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; };
}

Existem alguns poços a serem evitados, veja os comentários no código. Dependendo das suas necessidades, essa é uma alternativa sólida e extensível às enums.

sulai
fonte
1
pode ser bom se você precisar apenas de um ordinal para instâncias. Mas enums também possuem uma propriedade de nome que é bastante útil.
inor
6

É assim que eu aprimoro o padrão de herança de enum com verificação de tempo de execução no inicializador estático. As BaseKind#checkEnumExtenderverificações que "estendendo" a enum declaram todos os valores da enum base exatamente da mesma maneira, #name()e#ordinal() permanecem totalmente compatíveis.

Ainda há copiar-colar envolvido para declarar valores, mas o programa falha rapidamente se alguém adiciona ou modifica um valor na classe base sem atualizar os estendidos.

Comportamento comum para enums diferentes que se estendem:

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 base, com método de verificação:

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

}

Amostra de extensão:

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

}
Laurent Caillette
fonte
4

Baseado em @Tom Hawtin - resposta simplificada que adicionamos suporte a 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:
}
Khaled Lela
fonte
Qual a utilidade do valueOf()método?
Axel Advento
@AxelAdvento A idéia aqui é que dependemos da interface Dayque possui o método valueOf()então switch(Day.valueOf()), é implementada por WeekDay, WeekEndDayenumerações.
Khaled Lela
3

Eu sugiro que você faça o contrário.

Em vez de estender a enumeração existente, crie uma maior e crie um subconjunto dela. Por exemplo, se você tivesse uma enumeração chamada PET e desejasse estendê-la para ANIMAL, faça o seguinte:

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

Tenha cuidado, animais de estimação não são coleções imutáveis; você pode usar o Guava ou Java9 para obter mais segurança.

Guillaume Robbe
fonte
2

Tendo tido esse mesmo problema, gostaria de postar minha perspectiva. Eu acho que existem alguns fatores motivadores para fazer algo assim:

  • Você deseja ter alguns códigos de enumeração relacionados, mas em classes diferentes. No meu caso, eu tinha uma classe base com vários códigos definidos em um enum associado. Em uma data posterior (hoje!), Eu queria fornecer algumas novas funcionalidades para a classe base, o que também significou novos códigos para a enumeração.
  • A classe derivada suportaria a enumeração das classes base e a sua própria. Sem valores de enumeração duplicados! Então: como ter um enum para a subclasse que inclua o enum de seu pai junto com seus novos valores.

O uso de uma interface não resolve: você pode acidentalmente obter valores de enumeração duplicados. Não é desejável.

Acabei combinando as enumerações: isso garante que não haja valores duplicados, às custas de estar menos vinculado à sua classe associada. Mas achei que a questão duplicada era minha principal preocupação ...

dsummersl
fonte
2

Como um auxílio para entender por que estender um Enum não é razoável no nível de implementação de linguagem para considerar o que aconteceria se você passasse uma instância do Enum estendido para uma rotina que apenas entenda o Enum de base. Uma opção que o compilador prometeu ter todos os casos cobertos na verdade não cobriria esses valores estendidos do Enum.

Isso enfatiza ainda mais que os valores do Java Enum não são números inteiros, como os C, por exemplo: para usar um Java Enum como um índice de matriz, você deve solicitar explicitamente seu membro ordinal () para fornecer a um Java Enum um valor inteiro arbitrário que você deve adicionar um campo explícito para isso e referencie esse membro nomeado.

Este não é um comentário sobre o desejo do OP, apenas sobre o motivo pelo qual o Java nunca o fará.

user2543191
fonte
1

Na esperança de que esta solução elegante de um colega meu seja vista neste longo post, eu gostaria de compartilhar essa abordagem para subclasses que segue a abordagem da interface e além.

Esteja ciente de que usamos exceções personalizadas aqui e esse código não será compilado, a menos que você o substitua por suas exceções.

A documentação é extensa e espero que seja compreensível para a maioria de vocês.

A interface que todo enum subclasse precisa implementar.

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

A classe base ENUM de implementação.

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

A subclasse ENUM que "herda" da 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, o ParameterImpl genérico para adicionar alguns utilitários.

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

Minha maneira de codificar seria a seguinte:

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

LinkedHashSetfornece que cada entrada existe apenas uma vez e que seu pedido é preservado. Se a ordem não importa, você pode usar HashSet. O código a seguir não é possível em 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.");
    }
}

O código pode ser escrito da seguinte maneira:

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

A partir do Java 7, você pode fazer o mesmo com 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 a substituição de 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.");
    }
}
Matthias Ronge
fonte