Les énumérations peuvent-elles être sous-classées pour ajouter de nouveaux éléments?


Je veux prendre une énumération existante et y ajouter plus d'éléments comme suit:

enum A {a,b,c}

enum B extends A {d}

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

Est-ce possible en Java?

 452
Author: Jacek Laskowski, 2009-09-12

15 answers

Non, vous ne pouvez pas le faire en Java. En dehors de toute autre chose, d serait alors vraisemblablement une instance de A (étant donné l'idée normale de "extends"), mais les utilisateurs qui ne connaissaient que A ne le sauraient pas - ce qui va à l'encontre du fait qu'une énumération soit un ensemble de valeurs bien connu.

Si vous pouviez nous en dire plus sur la façon dont vous voulez utiliser cela, nous pouvons proposer des solutions alternatives.

 381
Author: Jon Skeet, 2009-09-12 09:53:04

Les énumérations représentent une énumération complète des valeurs possibles. Donc, la réponse (inutile) est non.

Comme exemple d'un problème réel, prenez les jours de semaine, les jours de week-end et, le syndicat, les jours de semaine. Nous pourrions définir tous les jours dans les jours de la semaine, mais nous ne serions pas en mesure de représenter des propriétés spéciales pour les jours de la semaine et les jours du week-end.

Ce que nous pourrions faire, c'est avoir trois types d'énumération avec un mappage entre les jours de la semaine/week-end et les jours de la semaine.

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

Alternativement, nous pourrait avoir une interface ouverte pour le jour de la semaine:

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

, Ou nous pourrions combiner les deux approches:

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() { ... }
}
 293
Author: Tom Hawtin - tackline, 2009-09-12 11:24:00

La solution recommandée à cela est le extensible enum pattern.

Cela implique de créer une interface et d'utiliser celle où vous utilisez actuellement l'énumération. Ensuite, faites en sorte que l'énumération implémente l'interface. Vous pouvez ajouter plus de constantes en faisant en sorte que cette nouvelle énumération étende également l'interface.

 64
Author: JodaStephen, 2012-02-21 11:00:26

Sous les couvertures, votre ÉNUMÉRATION n'est qu'une classe régulière générée par le compilateur. Cette classe générée étend java.lang.Enum. La raison technique pour laquelle vous ne pouvez pas étendre la classe générée est que la classe générée est final. Les raisons conceptuelles pour lesquelles il est définitif sont discutées dans ce sujet. Mais je vais ajouter la mécanique à la discussion.

Voici une énumération de test:

public enum TEST {  
    ONE, TWO, THREE;
}

Le code résultant de 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);
}

Il est concevable que vous puissiez taper cette classe par vous-même et supprimer le "final". Mais le compilateur vous empêche d'étendre "java.lang.Enum " directement. Vous pouvez décider de NE PAS étendre java.lang.Enum, mais alors votre classe et ses classes dérivées ne seraient pas une instanceof java.lang.Enum ... qui pourrait ne pas vraiment d'importance pour vous de toute façon!

 47
Author: ChrisCantrell, 2013-03-18 09:35:59
enum A {a,b,c}
enum B extends A {d}
/*B is {a,b,c,d}*/

Peut être écrit comme:

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

Comment cela peut-être utile:, supposons que nous voulons quelque chose comme: Nous avons des événements et nous utilisons enums. Ces énumérations peuvent être regroupées par traitement similaire. Si nous avons un fonctionnement avec de nombreux éléments, certains événements démarrent l'opération, certains ne sont que des étapes et d'autres terminent l'opération. Pour rassembler une telle opération et éviter de longs cas de commutation nous pouvons les regrouper comme dans l'exemple et utiliser:

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

Exemple:

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      
}  

Ajouter un peu plus avancé:

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

Ci-dessus si nous avons un échec (myEvent.is (State_StatusGroup. FAIL)) puis en itérant par des événements précédents, nous pouvons facilement vérifier si nous devons annuler le transfert d'argent par:

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

, Il peut être utile pour:

  1. y compris les méta-données explicites sur la logique de traitement, moins à retenir
  2. implémentation d'une partie du multi-héritage
  3. nous ne voulons pas utiliser de structures de classe, ex. pour envoyer des messages d'état courts
 25
Author: Waldemar Wosiński, 2014-08-12 11:15:48

Voici comment j'ai trouvé comment étendre une énumération dans une autre énumération, est une approche très directe:

Suposse vous avez une énumération avec des constantes communes:

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

Ensuite, vous pouvez essayer de faire un manuel s'étend de cette façon:

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

Bien sûr, chaque fois que vous avez besoin d'étendre une constante, vous devez modifier vos fichiers de sous-répertoire.

 10
Author: Juan Pablo G, 2015-01-03 15:38:54

Au cas où vous l'auriez manqué, il y a un chapitre dans l'excellent livre de Joshua Bloch " Java Effective, 2nd edition".

  • Chapitre 6-Énumérations et annotations
    • Item 34: Émuler des énumérations extensibles avec des interfaces

Extrait de ici.

Juste la conclusion :

Un inconvénient mineur de l'utilisation d'interfaces pour émuler des énumérations extensibles est que les implémentations ne peuvent pas être héritées d'une énumération type à l'autre. Dans le cas de notre exemple d'opération, la logique pour stocker et récupérer le symbole associé avec une opération est dupliqué dans BasicOperation et ExtendedOperation. Dans ce cas, cela n'a pas d'importance car très peu de code est dupliqué. Si il y avait un plus grande quantité de fonctionnalités partagées, vous pouvez l'encapsuler dans une classe d'assistance ou une méthode d'aide statique pour éliminer la duplication de code.

En résumé, alors que vous ne pouvez pas écrire un type d'énumération extensible, vous pouvez émulez-le en écrivant une interface pour aller avec un type d'énumération de base qui implémente interface. Cela permet aux clients d'écrire leurs propres énumérations qui implémentent interface. Ces énumérations peuvent ensuite être utilisées partout où le type d'énumération de base peut être utilisé, en supposant que les API sont écrites en termes d'interface.

 9
Author: Guillaume Husta, 2014-07-22 12:33:39

J'ai tendance à éviter les énumérations, car elles ne sont pas extensibles. Pour rester avec l'exemple de l'OP, si A est dans une bibliothèque et B dans votre propre code, vous ne pouvez pas prolonger Un si c'est un enum. Voici comment je remplace parfois les énumérations:

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

Il y a des fosses à éviter, voir les commentaires dans le code. Selon vos besoins, c'est une alternative solide et extensible aux énumérations.

 6
Author: sulai, 2012-09-09 10:58:36

C'est ainsi que j'améliore le modèle d'héritage enum avec la vérification d'exécution dans l'initialiseur statique. Le BaseKind#checkEnumExtender vérifie que l'énumération "extension" déclare toutes les valeurs de l'énumération de base exactement de la même manière, donc #name() et #ordinal() restent entièrement compatibles.

Il y a toujours du copier-coller impliqué pour déclarer des valeurs mais le programme échoue rapidement si quelqu'un a ajouté ou modifié une valeur dans la classe de base sans mettre à jour les extensions.

Comportement commun pour différentes énumérations s'étendant chacune autres:

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 de base, avec méthode de vérification:

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

}

exemple d'Extension:

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

}
 5
Author: Laurent Caillette, 2013-05-18 19:36:57

Ayant moi-même eu ce même problème, j'aimerais poster mon point de vue. Je pense qu'il y a quelques facteurs motivants pour faire quelque chose comme ça:

  • Vous voulez avoir des codes enum connexes, mais dans différentes classes. Dans mon cas, j'avais une classe de base avec plusieurs codes définis dans une énumération associée. À une date ultérieure (aujourd'hui!) Je voulais fournir de nouvelles fonctionnalités à la classe de base, ce qui signifiait également de nouveaux codes pour l'énumération.
  • La classe dérivée supporterait l'énumération des deux classes de base ainsi que la sienne. Pas de valeurs enum dupliquées! Alors: comment avoir une énumération pour la sous-classe qui inclut les énumérations de son parent avec ses nouvelles valeurs.

L'utilisation d'une interface ne le coupe pas vraiment: vous pouvez accidentellement obtenir des valeurs d'énumération en double. Pas souhaitable.

J'ai fini par simplement combiner les énumérations: cela garantit qu'il ne peut y avoir de valeurs en double, au détriment d'être moins étroitement lié à sa classe associée. Mais, j'ai pensé que la le problème des doublons était ma principale préoccupation...

 2
Author: dsummersl, 2011-07-21 20:50:22

Je vous suggère de prendre l'approche inverse.

Au lieu d'étendre l'énumération existante, créez-en une plus grande et créez-en un sous-ensemble. Par exemple, si vous aviez une énumération appelée PET et que vous vouliez l'étendre à ANIMAL, vous devriez le faire à la place:

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

Attention, pets n'est pas une collection immuable, vous voudrez peut-être utiliser Goyave ou Java9 pour plus de sécurité.

 2
Author: Guillaume Robbe, 2017-07-28 10:33:37

Pour aider à comprendre pourquoi l'extension d'une énumération n'est pas raisonnable au niveau de l'implémentation du langage pour considérer ce qui se passerait si vous transmettiez une instance de l'énumération étendue à une routine qui ne comprend que l'énumération de base. Un commutateur que le compilateur a promis avait tous les cas couverts ne couvrirait en fait pas ces valeurs d'énumération étendues.

Cela souligne en outre que les valeurs d'énumération Java ne sont pas des entiers tels que C, par exemple: pour utiliser une énumération Java comme index de tableau, vous doit demander explicitement son membre ordinal (), pour donner à une énumération java une valeur entière arbitraire, vous devez ajouter un champ explicite pour cela et référencer ce membre nommé.

Ce n'est pas un commentaire sur le désir de l'OP, juste sur la raison pour laquelle Java ne le fera jamais.

 2
Author: user2543191, 2018-07-26 14:08:19

Basé sur @ Tom Hawtin-tackline réponse nous ajoutons le support de commutateur,

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:
}
 1
Author: Khaled Lela, 2016-09-20 08:59:41

Dans l'espoir que cette solution élégante d'un de mes collègues soit même vue dans ce long post, j'aimerais partager cette approche pour le sous-classement qui suit l'approche de l'interface et au-delà.

Veuillez noter que nous utilisons des exceptions personnalisées ici et que ce code ne sera pas compilé sauf si vous le remplacez par vos exceptions.

La documentation est vaste et j'espère qu'elle est compréhensible pour la plupart d'entre vous.

L'interface dont chaque énumération sous-classée a besoin pour mettre.

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 de base ENUM implémentante.

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

L'énumération sous-classée qui "hérite" de la classe de 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);
  }
}

Enfin le ParameterImpl générique pour ajouter quelques utilitaires.

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());
  }
}
 1
Author: Dr4gon, 2016-11-10 16:15:58

Ma façon de coder ce serait comme suit:

// 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 fournit à la fois que chaque entrée n'existe qu'une seule fois et que leur ordre est préservé. Si l'ordre n'a pas d'importance, vous pouvez utiliser HashSet à la place. Le code suivant est impossible en 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.");
    }
}

Le code peut être écrit comme suit:

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

À partir de Java 7, vous pouvez même faire la même chose avec 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");
}

En utilisant le remplacement 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.");
    }
}
 0
Author: Paramaeleon, 2015-08-11 07:29:19