Can enums be subclassed to add new elements?

前端 未结 15 1831
臣服心动
臣服心动 2020-11-22 12:02

I want to take an existing enum and add more elements to it as follows:

enum A {a,b,c}

enum B extends A {d}

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

Is this po

相关标签:
15条回答
  • 2020-11-22 12:18

    The recommended solution to this is the extensible enum pattern.

    This involves creating an interface and using that where you currently use the enum. Then make the enum implement the interface. You can add more constants by making that new enum also extend the interface.

    0 讨论(0)
  • 2020-11-22 12:18

    This is how I enhance the enum inheritance pattern with runtime check in static initializer. The BaseKind#checkEnumExtender checks that "extending" enum declares all the values of the base enum in exactly the same way so #name() and #ordinal() remain fully compatible.

    There is still copy-paste involved for declaring values but the program fails fast if somebody added or modified a value in the base class without updating extending ones.

    Common behavior for different enums extending each other:

    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, with verifying method:

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

    Extension sample:

    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 ;
      }
    
    }
    
    0 讨论(0)
  • 2020-11-22 12:20

    Under the covers your ENUM is just a regular class generated by the compiler. That generated class extends java.lang.Enum. The technical reason you can't extend the generated class is that the generated class is final. The conceptual reasons for it being final are discussed in this topic. But I'll add the mechanics to the discussion.

    Here is a test enum:

    public enum TEST {  
        ONE, TWO, THREE;
    }
    

    The resulting code from 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);
    }
    

    Conceivably you could type this class on your own and drop the "final". But the compiler prevents you from extending "java.lang.Enum" directly. You could decide NOT to extend java.lang.Enum, but then your class and its derived classes would not be an instanceof java.lang.Enum ... which might not really matter to you any way!

    0 讨论(0)
  • 2020-11-22 12:22

    In case you missed it, there's a chapter in the excellent Joshua Bloch's book "Effective Java, 2nd edition".

    • Chapter 6 - Enums and Annotations
    • Item 34 : Emulate extensible enums with interfaces

    Just the conclusion :

    A minor disadvantage of the use of interfaces to emulate extensible enums is those implementations cannot be inherited from one enum type to another. In the case of our Operation example, the logic to store and retrieve the symbol associated with an operation is duplicated in BasicOperation and ExtendedOperation. In this case, it doesn’t matter because very little code is duplicated. If there were a a larger amount of shared functionality, you could encapsulate it in a helper class or a static helper method to eliminate the code duplication.

    In summary, while you cannot write an extensible enum type, you can emulate it by writing an interface to go with a basic enum type that implements the interface. This allows clients to write their own enums that implement the interface. These enums can then be used wherever the basic enum type can be used, assuming APIs are written in terms of the interface.

    0 讨论(0)
  • 2020-11-22 12:23

    Enums represent a complete enumeration of possible values. So the (unhelpful) answer is no.

    As an example of a real problem take weekdays, weekend days and, the union, days of week. We could define all days within days-of-week but then we would not be able to represent properties special to either weekdays and weekend-days.

    What we could do, is have three enum types with a mapping between weekdays/weekend-days and days-of-week.

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

    Alternatively, we could have an open-ended interface for day-of-week:

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

    Or we could combine the two approaches:

    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() { ... }
    }
    
    0 讨论(0)
  • 2020-11-22 12:25

    Here is a way how I found how to extend a enum into other enum, is a very straighfoward approach:

    Suposse you have a enum with common constants:

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

    then you can try to do a manual extends in this way:

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

    of course every time you need to extend a constant you have to modify your SubEnum files.

    0 讨论(0)
提交回复
热议问题