Question Les enums peuvent-ils être sous-classés pour ajouter de nouveaux éléments?


Je veux prendre une énumération existante et 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?


441
2017-09-12 09:50


origine


Réponses:


Non, vous ne pouvez pas le faire en Java. Mis à part autre chose, d serait alors probablement une instance de A (donné l'idée normale de "étend"), mais les utilisateurs qui connaissaient seulement A ne serait pas au courant - ce qui va à l'encontre du point de départ étant un ensemble de valeurs bien connu.

Si vous pouviez nous en dire plus sur la façon dont vous voulez utilisation ceci, nous pourrions potentiellement suggérer des solutions alternatives.


370
2017-09-12 09:53



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

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

Ce que nous pourrions faire, c'est avoir trois types de énumérations avec une correspondance entre les jours de 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 pourrions 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() { ... }
}

289
2017-09-12 11:24



La solution recommandée est la motif enum extensible.

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


65
2017-09-12 11:18



Sous les couvertures votre ENUM est juste une classe régulière générée par le compilateur. Cette classe générée s'é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 que ce soit définitif sont discutées dans ce sujet. Mais je vais ajouter les mécanismes à la discussion.

Voici un test enum:

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

En théorie, vous pouvez taper cette classe par vous-même et laisser tomber la "finale". Mais le compilateur vous empêche d'étendre directement "java.lang.Enum". Vous pourriez décider de ne pas étendre java.lang.Enum, mais votre classe et ses classes dérivées ne seraient pas une instance de java.lang.Enum ... ce qui ne vous importerait peut-être pas du tout!


46
2017-11-17 13:34



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: Disons que nous voulons quelque chose comme: Nous avons des événements et nous utilisons des enums. Ces énumérations peuvent être regroupées par un traitement similaire. Si nous avons des opérations avec de nombreux éléments, alors certains événements démarrent, certains ne sont que l'étape et une autre fin de l'opération. Pour rassembler une telle opération et éviter un long 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      
}  

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

Au-dessus, si nous avons des échecs (myEvent.is (State_StatusGroup.FAIL)) et que nous répétons les événements précédents, nous pouvons facilement vérifier si nous devons annuler le transfert d'argent en:

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

Cela peut être utile pour:

  1. y compris des méta-données explicites sur la logique de traitement, moins à retenir
  2. mettre en œuvre une partie de l'héritage multiple
  3. nous ne voulons pas utiliser les structures de classes, ex. pour envoyer des messages d'état courts

23
2018-06-06 13:28



Voici une façon dont j'ai trouvé comment étendre une énumération dans une autre énumération, est une approche très simple:

Suposse vous avez un enum 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;
    }
}

alors vous pouvez essayer de faire un manuel de cette manière:

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 SubEnum.


10
2017-12-27 18:19



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

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

Extrait ici.

Juste la conclusion:

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

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


9
2017-07-22 11:09



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 étendre A s'il s'agit d'une énumération. 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 quelques fosses à éviter, voir les commentaires dans le code. Selon vos besoins, il s'agit d'une alternative solide et extensible aux enums.


6
2017-09-09 10:58



C'est ainsi que j'améliore le modèle d'héritage enum avec la vérification de l'exécution dans l'initialisateur statique. le BaseKind#checkEnumExtender vérifie que "extend" enum déclare toutes les valeurs de l'énumération de base exactement de la même façon #name() et #ordinal() rester entièrement compatible.

Il y a encore du copier-coller 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 uns.

Comportement commun aux différentes énumérations qui s’étendent mutuellement:

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

Base enum, 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 ) ;
    }
  }

}

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

}

4
2018-05-18 19:36



Ayant eu ce même problème moi-même, je voudrais poster ma perspective. Je pense qu'il y a quelques facteurs de motivation pour faire quelque chose comme ça:

  • Vous voulez avoir des codes énumérés, mais dans des classes différentes. Dans mon cas, j'avais une classe de base avec plusieurs codes définis dans un enum associé. À 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 prendrait en charge l'énumération des classes de base ainsi que la sienne. Aucune valeur enum en double! Donc: comment avoir un enum pour la sous-classe qui inclut les enum de son parent avec ses nouvelles valeurs.

L'utilisation d'une interface ne le coupe pas vraiment: vous pouvez accidentellement obtenir des valeurs enum dupliquées. Pas souhaitable.

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


2
2017-07-21 20:50