问答中心分类: JAVA枚举是否可以子类化以添加新元素?
0
匿名用户 提问 18小时 前

我想获取现有枚举并向其中添加更多元素,如下所示:

enum A {a,b,c}

enum B extends A {d}

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

这在Java中可能吗?

14 Answers
0
Tom Hawtin - tackline 回答 18小时 前

枚举表示可能值的完整枚举。所以(毫无帮助的)答案是否定的。
作为一个实际问题的例子,以工作日、周末和工会的工作日为例。我们可以在一周中的几天内定义所有天,但这样我们就无法表示工作日和周末的特殊属性。
我们可以做的是,有三种枚举类型,在工作日/周末和一周中的几天之间进行映射。

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

或者,我们可以为一周中的某一天提供一个开放式界面:

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

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

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() { ... }
}
Manius 回复 18小时 前

这有什么问题吗?switch语句在接口上不起作用,但在常规枚举上起作用。不使用开关会扼杀枚举的一个优点。

Tom Hawtin - tackline 回复 18小时 前

@十字军战士第二种方法,是的,你必须使用ifelse链子什么的。第一种方法没有接口。对于第三个,你需要打电话toDayOfWeek如果你有Day参考

Snekse 回复 18小时 前

我想这可能还有另一个问题。工作日之间没有平等。周一和周日。周一。这难道不是Enum的另一大好处吗?我没有更好的解决方案,只是意识到这一点,因为我正在努力找到最好的答案。缺少使用==的能力会使手有点紧张。

Tom Hawtin - tackline 回复 18小时 前

@Snekse如果您意外地比较WeekdayDayOfWeek.MON,或类似使用Object.equals,Map.get,List.indexOf等(参见斯莫维格。blogspot。co.uk/2007/12/…)

djechlin 回复 18小时 前

@十字军战士是的,这正是取舍。如果想要可扩展的东西,就不能有固定的switch语句;如果想要一组固定的已知值,就不能有可扩展的东西。

Joshua Goldberg 回复 18小时 前

从enum到interface,还将丢失对values()的静态调用。这使得重构变得困难,尤其是当您决定扩展枚举并将接口作为抽象屏障添加到已建立的枚举时。

Jatin Sehgal 回复 18小时 前

你已经完成了双向飞碟!!

Klitos Kyriacou 回复 18小时 前

Java 1.7 API使用这种从接口派生枚举的方法,例如Java。nio。文件文件夹。write()将OpenOption数组作为最后一个参数。OpenOption是一个接口,但当我们调用此函数时,通常会传递一个标准OpenOption枚举常量,该常量派生自OpenOption。这有可扩展的优点,但也有缺点。该实现受到OpenOption是一个接口这一事实的影响。它创建一个哈希集从传递的数组中,当它可以创建更节省空间和时间的枚举集时。而且它不能使用开关。

0
JodaStephen 回答 18小时 前

建议的解决方案是可扩展枚举模式.
这涉及到创建一个接口,并在当前使用枚举的地方使用该接口。然后让枚举实现接口。您可以通过添加额外的枚举/类来添加更多常量,该枚举/类也扩展了接口。总体思路如下:

public interface TrafficLights {
  public abstract String getColour();
}
public enum StandardTrafficLights implements TrafficLights {
  RED, YELLOW, GREEN;
  public String getColour() {
    return name();
  }
}
public enum WeirdTrafficLights implements TrafficLights {
  DOUBLE_RED;
  public String getColour() {
    return name();
  }
}

请注意,如果您想要TrafficLights.valueof(String)你必须自己实施。

Tim Clemons 回复 18小时 前

值得一提的是,他们在接口中使用了工厂方法。考虑到扩展不是可行的解决方案,在相关枚举之间共享公共功能的好方法。

Dherik 回复 18小时 前

能否提供有关此模式的更多详细信息(代码:)?

Eria 回复 18小时 前

该模式不允许扩展枚举的值。这就是问题的关键所在。

douira 回复 18小时 前

遗憾的是,这个答案现在已经没有多大帮助了,因为它所依赖的链接现在已经不存在了。

0
ChrisCantrell 回答 18小时 前

实际上,枚举只是编译器生成的一个常规类。生成的类扩展java.lang.Enum. 无法扩展生成的类的技术原因是生成的类final. 本主题将讨论其最终定案的概念原因。但我会在讨论中加入一些技巧。
以下是测试枚举:

public enum TEST {  
    ONE, TWO, THREE;
}

javap生成的代码:

public final class TEST extends java.lang.Enum {
  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);
}

可以想象,你可以自己键入这个类,然后去掉“final”。但是编译器阻止您直接扩展“java.lang.Enum”。您可以决定不扩展java。但是您的类及其派生类将不是java的实例。lang.枚举。。。这对你来说可能并不重要!

soote 回复 18小时 前

空静态块在做什么?”静态{};’

ChrisCantrell 回复 18小时 前

它里面没有代码。“javap”程序显示空块。

soote 回复 18小时 前

如果它不起作用的话,它就在那里很奇怪,不是吗?

ChrisCantrell 回复 18小时 前

你是对的!我的错误。它不是一个空的代码块。如果运行“javap-c”,您将看到静态块中的实际代码。静态块创建所有枚举实例(此处为一个、两个和三个)。对此表示抱歉。

Brick 回复 18小时 前

为了好玩,我试着输入你上的课javap直接看我是否能放弃决赛。没有骰子。编译器不允许我扩展Enum明确地(我是否给班级打电话无关紧要final-尝试扩展时出错Enum.)

Benjamin 回复 18小时 前

感谢您直截了当地陈述这一事实:因为java。lang.Enum声明为final。

Holger 回复 18小时 前

@Brick:当您使用更老的编译器,它不知道扩展Enum不允许手动操作。

0
Waldemar Wosiński 回答 18小时 前
enum A {a,b,c}

enum B extends A {d}

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

可以写为:

public enum All {
    a       (ClassGroup.A,ClassGroup.B),
    b       (ClassGroup.A,ClassGroup.B),
    c       (ClassGroup.A,ClassGroup.B),
    d       (ClassGroup.B) 
...
  • 类别组。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()..

例子:

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 getMembers();
    void addMember(AtmOperationStatus pE);
}
// DEFINING GROUPS
public enum State_StatusGroup implements StatusGroupInterface{
    START, STEP, FAIL, FINISH;

    private List members = new LinkedList();

    @Override
    public EnumSet 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      
}

添加一些更高级的:

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

在上面,如果我们有一些失败(myEvent.is(State\u StatusGroup.fail)),那么通过重复前面的事件,我们可以轻松检查是否必须通过以下方式恢复资金转账:

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

它可用于:

  1. 包括关于处理逻辑的明确元数据,需要记住的更少
  2. 实现一些多重继承
  3. 我们不想使用类结构,例如用于发送短状态消息
HopefullyHelpful 回复 18小时 前

这是解决这个问题的绝妙办法。

0
Guillaume Husta 回答 18小时 前

如果你错过了,在约书亚·布洛赫的书中有一章”有效Java,第2版“.

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

结论如下:

使用接口模拟可扩展枚举的一个小缺点是

总之,虽然不能编写可扩展的枚举类型,但可以

Joel 回复 18小时 前

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