可以枚举枚举以添加新元素吗?

Mik*_*ike 508 java enums

我想采用现有的枚举并添加更多元素,如下所示:

enum A {a,b,c}

enum B extends A {d}

/*B is {a,b,c,d}*/
Run Code Online (Sandbox Code Playgroud)

这在Java中可行吗?

Jon*_*eet 432

不,你不能用Java做到这一点.除了其他任何东西之外,d大概可能是A(给出"扩展"的正常概念)的一个实例,但是只知道A它的用户不会知道它 - 这会使枚举点成为一个众所周知的集合值.

如果您可以告诉我们更多关于您希望如何使用它的信息,我们可以建议其他解决方案.

  • 所有枚举都隐式扩展了java.lang.Enum.由于Java不支持多重继承,因此枚举不能扩展其他任何内容. (506认同)
  • @givanse ...不同意你的意思是java.lang.Enum的隐式扩展是非继承的原因,因为java中的每个类也隐式继承了Object类,但它可以继承其他类,因为它会来在层次结构中,作为`Ob​​ject-> A-> B`而不是`Object-> A-> B扩展Object` (42认同)
  • @Tyler:C#枚举只是与数字相关联的名称,没有自动验证或*任何*.IMO枚举是Java的一点,实际上比C#好. (28认同)
  • 这里没有@JonSkeet.在我的用例中,我想在我的大枚举中分离所有讨厌的逻辑,并将逻辑隐藏起来,并定义一个干净的枚举,扩展另一个隐藏的枚举.具有大量逻辑的枚举打败了声明清理变量的想法,因此您不必声明数百个静态字符串变量,因此具有5个枚举的类不会变得不可读且行太大.我不希望其他开发人员关心复制和粘贴下一个项目的代码安静,而是扩展base_enum ......这对我来说很有意义...... (21认同)
  • 我想扩展的原因是因为我想要一个名为IntEnum的基类,它看起来像这样:http://stackoverflow.com/questions/1681976/enum-with-int-value-in-java/ 1682018#1682018.然后我的所有枚举都可以扩展它......在这种情况下,只是从继承中受益,因此我不必经常复制这个"基于int的枚举"代码.我是Java的新手,来自C#,我希望我能错过一些东西.我目前的观点是,与C#相比,Java枚举是一种痛苦. (9认同)
  • 完全不同意@givanse和@npiv:这与@mickeymoon正确解释的多重继承无关.真正的原因是因为`extends`是一个"is-a"关系,因此子类可以在需要超类的任何地方使用,但是(使用OP的例子)`d`不是类型`A的有效值. ,所以不能在需要A的地方传递.(顺便说一句,扩展枚举的唯一方法是概念上适合Java的扩展模型,如果子枚举只允许其超级枚举的子集;在数学中思考集合论.) (7认同)
  • 你之前完全没有这样做的原因:enum意味着一组众所周知的价值观.如果你可以添加元素,任何使用它的现有代码都可能会突然受到惊吓. (2认同)
  • @nasch问题不在于扩展一个类,而在于扩展另一个类.是的,如果枚举不是隐式最终的,那么缺少多重继承支持会阻止它们扩展非枚举类.但问题是为什么一个枚举不能扩展另一个枚举.答案是:因为枚举是最终的. (2认同)

Tom*_*ine 307

枚举代​​表可能值的完整枚举.所以(无益的)答案是否定的.

作为一个真实问题的一个例子,需要工作日,周末和工会,一周中的几天.我们可以定义星期几内的所有日期,但是我们无法在工作日和周末日表示特殊属性.

我们可以做的是,有三种枚举类型,在工作日/周末 - 日和星期几之间进行映射.

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;
}
Run Code Online (Sandbox Code Playgroud)

或者,我们可以为星期几开放一个开放式界面:

interface Day {
    ...
}
public enum Weekday implements Day {
    MON, TUE, WED, THU, FRI;
}
public enum WeekendDay implements Day {
    SAT, SUN;
}
Run Code Online (Sandbox Code Playgroud)

或者我们可以将两种方法结合起来:

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() { ... }
}
Run Code Online (Sandbox Code Playgroud)

  • 这不是有问题吗?switch语句不适用于接口,但它适用于常规枚举.没有工作w/switch类型杀死枚举的一个更好的事情. (19认同)
  • 我想这可能还有其他问题.Weekday.MON和DayOfWeek.MON之间没有平等关系.这不是枚举的另一大好处吗?我没有更好的解决方案,只是意识到这一点,因为我正在努力寻找最佳答案.缺乏使用==强迫手一点. (9认同)
  • 这种从接口派生枚举的方法由Java 1.7 API使用,例如java.nio.file.Files.write()将OpenOption数组作为最后一个参数.OpenOption是一个接口,但是当我们调用这个函数时,我们通常会传递一个StandardOpenOption枚举常量,它来自OpenOption.这具有可扩展的优点,但也具有缺点.实现受到OpenOption是一个接口的影响.它可以从传递的数组创建一个HashSet <OpenOption>,它可以创建一个更节省空间和时间的EnumSet.它不能使用开关. (3认同)
  • @Crusader 是的,这正是权衡。如果你想要一些可扩展的东西,你就不能有固定的 switch 语句,如果你想要一组固定的已知值,你就不能有可扩展的东西。 (2认同)
  • 从枚举到接口,您也会丢失对values()的静态调用.这使得重构变得困难,特别是如果您决定扩展枚举并将接口添加为已建立的枚举的抽象屏障. (2认同)

Jod*_*hen 70

推荐的解决方案是可扩展的枚举模式.

这涉及创建一个界面并使用当前使用枚举的地方.然后使枚举实现接口.您可以通过使新的枚举也扩展接口来添加更多常量.

  • 你能提供关于这种模式的更多细节(代码:))吗? (7认同)
  • 该模式不允许扩展枚举的值。这是问的重点。 (2认同)

Chr*_*ell 52

在封面下,您的ENUM只是编译器生成的常规类.生成的类扩展了java.lang.Enum.您无法扩展生成的类的技术原因是生成的类是final.本主题讨论了它最终的概念性原因.但我会在讨论中添加机制.

这是一个测试枚举:

public enum TEST {  
    ONE, TWO, THREE;
}
Run Code Online (Sandbox Code Playgroud)

来自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);
}
Run Code Online (Sandbox Code Playgroud)

可以想象,您可以自己键入此类并删除"最终".但编译器会阻止您直接扩展"java.lang.Enum".您可以决定不扩展java.lang.Enum,但是您的类及其派生类将不是java.lang.Enum的实例......这对您来说可能并不重要!

  • 你是对的!我的错.它不是一个空的代码块.如果运行"javap -c",您将在静态块中看到实际代码.静态块创建所有ENUM实例(此处为ONE,TWO和THREE).对于那个很抱歉. (4认同)

Wal*_*ski 26

enum A {a,b,c}
enum B extends A {d}
/*B is {a,b,c,d}*/
Run Code Online (Sandbox Code Playgroud)

可以写成:

public enum All {
    a       (ClassGroup.A,ClassGroup.B),
    b       (ClassGroup.A,ClassGroup.B),
    c       (ClassGroup.A,ClassGroup.B),
    d       (ClassGroup.B) 
...
Run Code Online (Sandbox Code Playgroud)
  • ClassGroup.B.getMembers()包含{a,b,c,d}

它是如何有用的:让我们说我们想要的东西:我们有事件,我们正在使用枚举.这些枚举可以通过类似的处理进行分组.如果我们有许多元素的操作,那么一些事件开始操作,一些事件只是步骤而另一个事件结束操作.要收集此类操作并避免长时间切换,我们可以按照示例对它们进行分组并使用:

if(myEvent.is(State_StatusGroup.START)) makeNewOperationObject()..
if(myEnum.is(State_StatusGroup.STEP)) makeSomeSeriousChanges()..
if(myEnum.is(State_StatusGroup.FINISH)) closeTransactionOrSomething()..
Run Code Online (Sandbox Code Playgroud)

例:

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      
}  
Run Code Online (Sandbox Code Playgroud)

添加一些更高级的:

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),
(...)
Run Code Online (Sandbox Code Playgroud)

在上面,如果我们有一些失败(myEvent.is(State_StatusGroup.FAIL))然后迭代前面的事件,我们可以很容易地检查我们是否必须通过以下方式恢复汇款:

if(myEvent2.is(ChangedMoneyAccountState_EventsGroup.CHANGED)) rollBack()..
Run Code Online (Sandbox Code Playgroud)

它可用于:

  1. 包括有关处理逻辑的明确的元数据,少记住
  2. 实现一些多继承
  3. 我们不想使用类结构,例如.用于发送短消息


Jua*_*o G 13

这是我如何找到如何将枚举扩展到其他枚举的方法,这是一种非常直接的方法:

你有一个常见常量的枚举:

public interface ICommonInterface {

    String getName();

}


public enum CommonEnum implements ICommonInterface {
    P_EDITABLE("editable"),
    P_ACTIVE("active"),
    P_ID("id");

    private final String name;

    EnumCriteriaComun(String name) {
        name= name;
    }

    @Override
    public String getName() {
        return this.name;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以尝试以这种方式进行手动扩展:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

当然,每次需要扩展常量时,都必须修改SubEnum文件.


Gui*_*sta 10

如果你错过了它,那么优秀的Joshua Bloch的书" Java Effective,2nd edition "中有一章.

  • 第6章 - 枚举和注释
    • 第34项:使用接口模拟可扩展枚举

在这里提取.

只是结论:

使用接口来模拟可扩展枚举的一个小缺点是,实现不能从一个枚举类型继承到另一个枚举类型.在我们的操作示例中,存储和检索与操作关联的符号的逻辑在BasicOperation和ExtendedOperation中重复.在这种情况下,它并不重要,因为很少的代码是重复的.如果存在大量共享功能,则可以将其封装在辅助类或静态帮助程序方法中以消除代码重复.

总之,虽然您无法编写可扩展的枚举类型,但您可以通过编写接口来使用实现接口的基本枚举类型来模拟它.这允许客户端编写自己的实现接口的枚举.然后可以在可以使用基本枚举类型的任何地方使用这些枚举,假设API是根据接口编写的.

  • 对于任何拥有更新的Effective Java,第三版的人:第 6 章第 38 项(第 176 页)讨论了相同的模式。章节和项目标题没有改变。 (2认同)

sul*_*lai 6

我倾向于避免使用枚举,因为它们不可扩展.要继续使用OP的示例,如果A在库中而B在您自己的代码中,则如果它是枚举,则不能扩展A. 这就是我有时会替换枚举的方式:

// access like enum: A.a
public class A {
    public static final A a = new A();
    public static final A b = new A();
    public static final A c = new A();
/*
 * In case you need to identify your constant
 * in different JVMs, you need an id. This is the case if
 * your object is transfered between
 * different JVM instances (eg. save/load, or network).
 * Also, switch statements don't work with
 * Objects, but work with int.
 */
    public static int maxId=0;
    public int id = maxId++;
    public int getId() { return id; }
}

public class B extends A {
/*
 * good: you can do like
 * A x = getYourEnumFromSomeWhere();
 * if(x instanceof B) ...;
 * to identify which enum x
 * is of.
 */
    public static final A d = new A();
}

public class C extends A {
/* Good: e.getId() != d.getId()
 * Bad: in different JVMs, C and B
 * might be initialized in different order,
 * resulting in different IDs.
 * Workaround: use a fixed int, or hash code.
 */
    public static final A e = new A();
    public int getId() { return -32489132; };
}
Run Code Online (Sandbox Code Playgroud)

有一些要避免的坑,请参阅代码中的注释.根据您的需要,这是枚举的可靠,可扩展的替代方案.


Lau*_*tte 6

这是我在静态初始化程序中使用运行时检查来增强枚举继承模式的方法.BaseKind#checkEnumExtender"扩展"枚举的检查以完全相同的方式声明基本枚举的所有值,#name()#ordinal()保持完全兼容.

仍然有复制粘贴用于声明值,但如果有人在基类中添加或修改了一个值而没有更新扩展值,程序就会快速失败.

相互扩展的不同枚举的常见行为:

public interface Kind {
  /**
   * Let's say we want some additional member.
   */
  String description() ;

  /**
   * Standard {@code Enum} method.
   */
  String name() ;

  /**
   * Standard {@code Enum} method.
   */
  int ordinal() ;
}
Run Code Online (Sandbox Code Playgroud)

基本枚举,带验证方法:

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

}
Run Code Online (Sandbox Code Playgroud)

扩展样本:

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

}
Run Code Online (Sandbox Code Playgroud)


Kha*_*ela 6

基于@Tom Hawtin -我们添加了切换支持,

interface Day<T> {
    ...
  T valueOf();
}

public enum Weekday implements Day<Weekday> {
    MON, TUE, WED, THU, FRI;
   Weekday valueOf(){
     return valueOf(name());
   }
}

public enum WeekendDay implements Day<WeekendDay> {
    SAT, SUN;
   WeekendDay valueOf(){
     return valueOf(name());
   }
}

Day<Weekday> wds = Weekday.MON;
Day<WeekendDay> wends = WeekendDay.SUN;

switch(wds.valueOf()){
    case MON:
    case TUE:
    case WED:
    case THU:
    case FRI:
}

switch(wends.valueOf()){
    case SAT:
    case SUN:
}
Run Code Online (Sandbox Code Playgroud)