枚举可以被子类化以添加新元素吗?


534

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

enum A {a,b,c}

enum B extends A {d}

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

这在Java中可能吗?


12
这样做的原因是为了测试其中存在无效的枚举值而不在核心源中引入无效的枚举值的情况。
Archimedes Trajano

是的,“语言”纯洁的例子。我认为需要“自动保存整数”集的“ bookeeper”节省劳动力的想法,就像在C ++中那样,因此您可以启动新集,作为旧集的扩展,从1+的最后一个值开始优先集,如果条目是命名的,则从“公共子集”继承名称。尽管Java枚举对此有一些好处,但是它缺少C ++枚举提供的简单的自动自动递增整数声明帮助。
彼得

4
实际上,当您用新值扩展枚举时,您创建的不是子类,而是超类。您可以在任何地方使用基本枚举值而不是“扩展”枚举,反之亦然,因此,根据Liskov替换原理,扩展枚举是基本枚举的超类。
伊利亚2014年

@Ilya ...是的,这是真的。我指出这个问题有明确的实际用例。为了论证,请考虑以下基本枚举:PrimaryColours; 想要通过添加新的颜色名称将其到Enum 是合理的PrimaryAndPastelColours。里斯科夫仍然​​是房间里的大象。因此,为什么不从以下基本Enum开始:AllMyColours-然后可以将 所有颜色类化为:PrimaryAndPastelColours,然后将其类化为:(PrimaryColours牢记层次结构)。Java也不允许这样做。

Answers:


450

不,您无法在Java中执行此操作。除了别的什么,它d大概是的一个实例A(考虑到“扩展”的正常想法),但是只知道A这一点的用户却一无所知-这违背了枚举作为一组著名的枚举的意义。价值观。

如果您可以告诉我们更多有关如何使用它的信息,我们可能会建议其他解决方案。


516
所有枚举都隐式扩展java.lang.Enum。由于Java不支持多重继承,因此枚举不能扩展任何其他内容。
Givanse

9
我想延长的原因是因为我想有一个名为如IntEnum一个基类,看起来像这样:stackoverflow.com/questions/1681976/enum-with-int-value-in-java/...。然后,我所有的枚举都可以对其进行扩展...在这种情况下,仅从继承中受益,因此,我不必频繁复制此“基于int的枚举”代码。我是Java的新手,来自C#,但我希望我能缺少一些东西。我目前的观点是,与C#相比,Java枚举是一种痛苦。
泰勒·科利尔

30
@Tyler:C#枚举仅仅是与数字关联的名称,没有自动验证或任何内容。IMO枚举是Java的一小部分,实际上比C#更好。
乔恩·斯基特

21
在这里不同意@JonSkeet。在我的用例中,我想将我的大枚举中所有讨厌的逻辑分开,将其隐藏起来,并定义一个干净的枚举来扩展另一个被隐藏的枚举。具有大量逻辑的枚举击败了声明干净变量的想法,因此您不必声明数百个静态字符串变量,因此具有5个枚举的类不会变得不可读且行太多。我也不希望其他开发人员也关心复制和粘贴下一个项目的代码安全性,而是扩展base_enum ...对我来说很有意义...
mmm

43
@givanse ...就java.lang.Enum的隐式扩展而言,与您不同意是非继承的原因,因为Java中的每个类也隐式继承了Object类,但它可以继承其他一些类成层次结构Object->A->B,而不是Object->A->B extends Object
mickeymoon

317

枚举代​​表可能值的完整枚举。因此,(无用的)答案是否定的。

作为一个实际问题的示例,以工作日,周末和工会为例。我们可以在一周中的几天内定义所有日期,但随后就无法表示平日和周末的特殊属性。

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

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() { ... }
}

20
这不是问题吗?switch语句不适用于接口,但适用于常规枚举。不使用w / switch类会杀死有关枚举的更好的东西之一。
Manius

9
我在想这可能还有另一个问题。Weekday.MON和DayOfWeek.MON之间没有相等的关系。这不是枚举的另一个大好处吗?我没有更好的解决方案,只是在寻求最佳答案时才意识到这一点。不能使用==会使手有点用力。
Snekse

2
@Crusader是的,这就是权衡。如果要扩展某些内容,则不能使用固定的switch语句;如果要一组固定的已知值,则从重言而论上就不能扩展。
djechlin

3
从枚举到接口,您还会丢失对values()的静态调用。这使重构变得困难,尤其是如果您决定扩展枚举并将接口添加为已建立枚举的抽象障碍时尤其如此。
约书亚·戈德堡

4
Java 1.7 API使用这种从接口派生枚举的方法,例如java.nio.file.Files.write()将OpenOption数组作为最后一个参数。OpenOption是一个接口,但是当我们调用此函数时,通常会传递一个StandardOpenOption枚举常量,该常量是从OpenOption派生的。这具有可扩展的优点,但是也具有缺点。该实现受到OpenOption是接口这一事实的困扰。当它可以创建空间和时间效率更高的EnumSet时,它将根据传递的数组创建HashSet <OpenOption>。而且它不能使用开关。
Klitos Kyriacou 2015年

71

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

这涉及创建一个接口,并在您当前使用枚举的位置使用该接口。然后使枚举实现接口。您可以通过使新枚举也扩展接口来添加更多常量。


值得指出他们在界面中使用工厂方法。鉴于扩展不是可行的解决方案,因此在相关枚举之间共享通用功能的好方法。
蒂姆·克莱蒙斯

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

3
该模式不允许扩展枚举的值。这是问的重点。
伊里亚(Eria)'18

55

在幕后,您的ENUM只是由编译器生成的常规类。生成的类扩展java.lang.Enum。您无法扩展生成的类的技术原因是生成的类是final。本主题讨论了最终确定的概念性原因。但是,我将在讨论中加入一些技巧。

这是一个测试枚举:

public enum TEST {  
    ONE, TWO, THREE;
}

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

可以想象,您可以自己输入此类,然后删除“ final”。但是编译器阻止您直接扩展“ java.lang.Enum”。您可以决定不扩展java.lang.Enum,但是您的类及其派生类将不是java.lang.Enum的实例……这对您实际上可能并不重要!


1
空的静态块在做什么?'静态的 {};'
soote

1
它没有代码。“ javap”程序显示空白块。
ChrisCantrell '16

奇怪的是,如果它什么都不做,就在那里吗?
soote

4
你是对的!我的错。它不是空的代码块。如果运行“ javap -c”,则会在静态块内看到实际的代码。静态块创建所有ENUM实例(此处为一个,两个和三个)。对于那个很抱歉。
ChrisCantrell '16

1
感谢您陈述直截了当的事实:因为java.lang.Enum被声明为final。
本杰明·

26
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) 
...
  • 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()..

例:

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      
}  

添加一些更高级的内容:

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_StatusGroup.FAIL)),则通过先前的事件进行迭代,我们可以轻松地检查是否必须通过以下方式恢复汇款:

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

它可用于:

  1. 包括关于处理逻辑的显式元数据,少记
  2. 实现一些多重继承
  3. 例如,我们不想使用类结构。用于发送简短状态消息

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

那么您可以尝试以这种方式进行手册扩展:

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

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


有趣的是,我们还可以使用枚举toString(),最后比较字符串;并使用switch,我们只需要将对象强制转换为已知的枚举即可;唯一的问题是2个开发人员扩展并创建相同的枚举id,然后尝试合并两个代码:),现在,我认为我理解为什么枚举应保持不可扩展性。
Aquarius Power

11

万一您错过了它,在Joshua Bloch出色的书“ Java Effective,第二版 ”中有一章。

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

在这里提取。

只是结论:

使用接口模拟可扩展枚举的一个次要缺点是,实现不能从一种枚举类型继承到另一种枚举类型。对于我们的Operation示例,在BasicOperation和ExtendedOperation中复制了存储和检索与操作关联的符号的逻辑。在这种情况下,没有关系,因为几乎没有代码被重复。如果共享功能更多,则可以将其封装在帮助器类或静态帮助器方法中,以消除代码重复。

总而言之,虽然您不能编写可扩展的枚举类型,但是可以通过编写一个接口来与实现该接口的基本枚举类型一起来模拟它。这允许客户端编写自己的实现该接口的枚举。然后,假定API是根据接口编写的,则可以在可以使用基本枚举类型的任何地方使用这些枚举。


6

我倾向于避免枚举,因为它们不可扩展。保留OP的示例,如果A在库中,而B在您自己的代码中,则如果A是枚举,则不能扩展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; };
}

有一些陷阱要避免,请参见代码中的注释。根据您的需要,这是枚举的可靠且可扩展的替代方案。


1
如果您只需要一些序数实例就可以了。但是枚举还具有非常有用的名称属性。
INOR

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

基本枚举,带有验证方法:

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

}

扩展样本:

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

基于@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:
}

valueOf()方法有什么用?
Axel Advento

@AxelAdvento这里的想法是我们赖以生存的界面上Day有方法valueOf(),然后switch(Day.valueOf()),它是由执行WeekDay, WeekEndDay枚举。
Khaled Lela

3

我建议您采取另一种方法。

与其扩展现有的枚举,不如创建一个较大的枚举并创建其子集。例如,如果您有一个称为PET的枚举并且想将其扩展为ANIMAL,则应改为:

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

注意,宠物不是一成不变的收藏,您可能希望使用Guava或Java9来提高安全性。


2

我本人也遇到过同样的问题,我想发表我的看法。我认为做这样的事情有两个激励因素:

  • 您希望有一些相关的枚举代码,但是要使用不同的类。就我而言,我有一个基类,在关联的枚举中定义了几个代码。稍后(今天!),我想为基类提供一些新功能,这也意味着枚举的新代码。
  • 派生类将支持基类的枚举以及它自己的枚举。没有重复的枚举值!所以:如何为子类创建一个枚举,包括其父级的枚举及其新值。

使用接口并不能真正减少它:您可能会意外地得到重复的枚举值。不可取。

最后,我仅合并了枚举:这样可以确保没有重复的值,但要避免与其关联的类紧密关联。但是,我认为重复的问题是我的主要关注点...


2

为了帮助理解为什么扩展Enum在语言实现级别上不合理,需要考虑如果将扩展Enum的实例传递给只理解基本Enum的例程会发生什么。编译器承诺的覆盖所有情况的开关实际上将不覆盖那些扩展的Enum值。

这进一步强调了Java Enum值不是像C这样的整数:例如,要使用Java Enum作为数组索引,您必须明确要求其ordinal()成员,为Java Enum提供任意整数值,您必须添加一个明确的字段,并引用该命名成员。

这不是对OP的期望的评论,而是关于Java永远不会这样做的原因。


1

希望在我的长篇文章中能看到我的一位同事的优雅解决方案,我想分享这种继承接口方法和其他方法的子类方法。

请注意,我们在此处使用自定义异常,除非您将其替换为异常,否则此代码将不会编译。

文档内容广泛,希望对大多数人来说都是可以理解的。

每个子类枚举都需要实现的接口。

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

实现的ENUM基类。

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

从基类“继承”的子类ENUM。

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

最后在通用的ParameterImpl中添加一些实用程序。

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

0

我的编码方式如下:

// 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提供每个条目仅存在一次,并保留其顺序。如果顺序无关紧要,则可以HashSet改用。以下代码在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.");
    }
}

该代码可以编写如下:

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

从Java 7开始,您甚至可以执行以下操作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");
}

使用枚举替换:

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.");
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.