【问题标题】:Can enums be subclassed to add new elements?可以对枚举进行子类化以添加新元素吗?
【发布时间】:2010-11-27 17:40:58
【问题描述】:

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

enum A {a,b,c}

enum B extends A {d}

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

这在 Java 中可行吗?

【问题讨论】:

  • 这样做的原因是为了测试存在无效枚举值而不在核心源中引入无效枚举值的情况。
  • 是“语言”纯度的一个例子。我认为需要的是“记账”节省劳动力的想法,即自动递增一组整数,就像 C++ 中的整数一样,因此您可以开始一个新集作为旧集的扩展,从 1+ 的最后一个值开始先前的集合,如果条目被命名,则从“公共子集”继承名称。尽管 java 枚举有一些优点,但它缺乏 C++ 枚举提供的简单的自动自增整数声明帮助。
  • 实际上,当您使用新值扩展枚举时,您创建的不是子类,而是超类。您可以在任何地方使用基枚举值而不是“扩展”枚举,但反之则不行,因此根据 Liskov 替换原则,扩展枚举是基枚举的超类。
  • @Ilya ...是的,这是真的。我指出这个问题有明确的实际用例。为了论证起见,考虑一个 base 枚举:PrimaryColours;通过添加新的颜色名称将其 super-class 到 Enum PrimaryAndPastelColours 是合理的。 Liskov 仍然是房间里的大象。那么为什么不从一个基本枚举开始:AllMyColours - 然后一个 might sub - 将所有颜色分类为:PrimaryAndPastelColours 和随后 sub-将此归类为:PrimaryColours(牢记层次结构)。 Java 也不允许这样做。

标签: java enums


【解决方案1】:

不,您不能在 Java 中执行此操作。除此之外,d 可能是A 的一个实例(考虑到“扩展”的正常概念),但只知道A 的用户不会知道它——这违背了枚举是一组众所周知的值。

如果您能告诉我们更多关于您希望如何使用此功能,我们可能会建议替代解决方案。

【讨论】:

  • 所有枚举都隐式扩展 java.lang.Enum。由于 Java 不支持多重继承,枚举不能扩展其他任何东西。
  • 我想要扩展的原因是因为我想要一个名为例如的基类。 IntEnum,看起来像这样:stackoverflow.com/questions/1681976/enum-with-int-value-in-java/…。然后我所有的枚举都可以扩展它......在这种情况下,只是受益于继承,因此我不必经常复制这个“基于int的枚举”代码。我是 Java 新手,来自 C#,我希望我错过了一些东西。我目前的观点是,与 C# 相比,Java 枚举是一种痛苦。
  • @Tyler:C# 枚举只是与数字相关的名称,没有自动验证或任何东西。 IMO 枚举是 Java 的一种,它实际上比 C# 更好。
  • 这里不同意@JonSkeet。在我的用例中,我想将我的大枚举中的所有讨厌的逻辑分开,并将逻辑隐藏起来,并定义一个干净的枚举来扩展另一个隐藏的枚举。具有大量逻辑的枚举优于声明干净变量的想法,因此您不必声明数百个静态字符串变量,因此具有 5 个枚举的类不会变得不可读且行数过大。我也不希望其他开发人员关心复制和粘贴下一个项目的代码,而是扩展 base_enum ......这对我来说很有意义......
  • @givanse... 不同意你关于 java.lang.Enum 的隐式扩展是非继承的原因,因为 java 中的每个类也隐式继承 Object 类但它可以继承其他一些类,因为它将作为Object->A->B 而不是Object->A->B extends Object 进入层次结构@
【解决方案2】:

推荐的解决方案是extensible enum pattern

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

【讨论】:

  • 值得一提的是他们在接口中使用了工厂方法。鉴于扩展不是一个可行的解决方案,在相关枚举之间共享通用功能的好方法。
  • 您能否提供有关此模式的更多详细信息(代码:))?
  • 该模式不允许扩展枚举的值。问题的重点是什么。
【解决方案3】:

枚举表示可能值的完整枚举。所以(无用的)答案是否定的。

作为一个实际问题的示例,以工作日、周末以及工会、星期几为例。我们可以定义星期几内的所有日子,但是我们将无法表示工作日和周末的特殊属性。

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

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

【讨论】:

  • 这不是有问题吗? switch 语句不适用于接口,但它适用于常规枚举。不使用 switch 会扼杀枚举的优点之一。
  • 我认为这可能还有另一个问题。 Weekday.MON 和 DayOfWeek.MON 之间不相等。这不是枚举的另一个大好处吗?我没有更好的解决方案,只是在我试图找到最佳答案时意识到这一点。无法使用 == 有点用力。
  • @Crusader 是的,这正是权衡。如果你想要一些可扩展的东西,你就不能有固定的 switch 语句,如果你想要一组固定的已知值,你就不能有可扩展的东西。
  • 从枚举到接口,你也失去了对 values() 的静态调用。这使得重构变得困难,尤其是当您决定扩展枚举并将接口作为抽象屏障添加到已建立的枚举时。
  • 这种从接口派生枚举的方法由 Java 1.7 API 使用,例如java.nio.file.Files.write() 将 OpenOption 数组作为最后一个参数。 OpenOption 是一个接口,但是当我们调用这个函数时,我们通常会传递一个 StandardOpenOption 枚举常量,它是从 OpenOption 派生的。这具有可扩展的优点,但也有缺点。该实现受到 OpenOption 是一个接口这一事实的影响。它从传递的数组创建一个 HashSet,它本可以创建一个更节省空间和时间的 EnumSet。而且不能用switch。
【解决方案4】:

我自己也遇到过同样的问题,我想发表我的观点。我认为做这样的事情有几个激励因素:

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

使用接口并不能真正解决问题:您可能会意外获得重复的枚举值。不可取。

我最终只是组合了枚举:这样可以确保不会有任何重复的值,但代价是与其关联的类的联系不那么紧密。但是,我认为重复问题是我主要关心的问题......

【讨论】:

    【解决方案5】:
    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. 我们不想使用类结构,例如。用于发送简短的状态消息

    【讨论】:

    • 这是解决问题的绝妙方法。
    【解决方案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; };
    }
    

    有一些坑要避免,见代码中的cmets。根据您的需要,这是一个可靠的、可扩展的枚举替代方案。

    【讨论】:

    • 如果您只需要一些序数作为实例,它可能会很好。但是枚举也有一个非常有用的名称属性。
    【解决方案7】:

    实际上,您的 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 的一个实例……这对你来说可能并不重要!

    【讨论】:

    • 空的静态块在做什么? '静态{};'
    • 里面没有代码。 “javap”程序显示空块。
    • 如果它什么也没做,有它在那儿很奇怪,不是吗?
    • 你是对的!我的错。它不是一个空的代码块。如果您运行“javap -c”,您将在静态块中看到实际代码。静态块创建所有 ENUM 实例(此处为一个、两个和三个)。对此感到抱歉。
    • 感谢您陈述直截了当的事实:因为 java.lang.Enum 被声明为 final。
    【解决方案8】:

    这就是我如何通过静态初始化程序中的运行时检查来增强枚举继承模式。 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 ;
      }
    
    }
    

    【讨论】:

      【解决方案9】:

      如果你错过了,乔舒亚·布洛赫 (Joshua Bloch) 的名著“Effective Java, 2nd edition”中有一个章节。

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

      只是结论:

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

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

      【讨论】:

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

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

      假设你有一个带有通用常量的枚举:

      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,然后尝试合并两个代码:),现在我想我明白为什么枚举应该保持不可扩展了。
      【解决方案11】:

      我的编码方式如下:

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

      【讨论】:

        【解决方案12】:

        基于@Tom Hawtin - tackline 答案,我们添加了开关支持,

        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()方法有什么用?
        • @AxelAdvento 这里的想法是我们依赖于接口Day,它有方法valueOf()然后switch(Day.valueOf()),它是由WeekDay, WeekEndDay枚举实现的。
        【解决方案13】:

        希望我的一位同事的这个优雅的解决方案甚至可以在这篇长文中看到,我想分享这种遵循接口方法及其他方法的子类化方法。

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

        文档内容丰富,我希望你们大多数人都能理解。

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

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

        【讨论】:

          【解决方案14】:

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

          这进一步强调 Java Enum 值不是整数,例如 C 是:例如:要将 Java Enum 用作数组索引,您必须显式请求其 ordinal() 成员,以便为 Java Enum 提供任意整数值您必须为此添加一个显式字段并引用该命名成员。

          这不是对 OP 的愿望的评论,只是关于为什么 Java 永远不会这样做。

          【讨论】:

            【解决方案15】:

            我建议你采取相反的方法。

            不要扩展现有的枚举,而是创建一个更大的枚举并创建它的子集。 例如,如果您有一个名为 PET 的枚举,并且您想将其扩展到 ANIMAL,您应该这样做:

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

            请注意,pets 不是不可变的集合,您可能希望使用 Guava 或 Java9 以提高安全性。

            【讨论】:

              猜你喜欢
              • 2010-10-03
              • 1970-01-01
              • 1970-01-01
              • 2010-11-02
              • 1970-01-01
              • 1970-01-01
              • 2015-11-29
              • 2014-02-13
              相关资源
              最近更新 更多