【问题标题】:Convert from enum ordinal to enum type从枚举序数转换为枚举类型
【发布时间】:2010-10-11 05:24:54
【问题描述】:

我有枚举类型ReportTypeEnum,它在我所有类的方法之间传递,但我需要在 URL 上传递它,所以我使用序数方法来获取 int 值。在我的另一个 JSP 页面中得到它之后,我需要将它转换回 ReportTypeEnum 以便我可以继续传递它。

如何将序数转换为ReportTypeEnum

使用 Java 6 SE。

【问题讨论】:

  • 到目前为止还没有 Java 6 EE (AFAIK)。有 Java SE 6 和 Java EE 5。

标签: java enums


【解决方案1】:

安全第一(使用 Kotlin):

// Default to null
EnumName.values().getOrNull(ordinal)

// Default to a value
EnumName.values().getOrElse(ordinal) { EnumName.MyValue }

【讨论】:

    【解决方案2】:

    如果我要经常使用values()

    enum Suit {
       Hearts, Diamonds, Spades, Clubs;
       public static final Suit values[] = values();
    }
    

    同时where.java:

    Suit suit = Suit.values[ordinal];
    

    如果您希望数组是私有的,请成为我的客人:

    private static final Suit values[] = values();
    public static Suit get(int ordinal) { return values[ordinal]; }
    
    ...
    
    Suit suit = Suit.get(ordinal);
    

    注意你的数组边界。

    【讨论】:

    • +1 这是迄今为止最好的解决方案恕我直言,因为可以传递序数,尤其是在 android.os.Message 中。
    • 这非常令人担忧,因为数组是可变的。即使 values[] 是最终的,它也不会阻止代码中某处的Suit.values[0] = Suit.Diamonds;。理想情况下,这永远不会发生,但不要暴露可变字段的总体原则仍然成立。对于这种方法,请考虑改用Collections.unmodifiableList 或类似方法。
    • 怎么样 - private static final Suit values[] = values(); public static Suit[] getValues() { 返回值; }
    • @Pratyush 将使数组变量不可变,但其内容不可变。我仍然可以做 getValues()[0]=somethingElse;
    • 同意,因此重点不是直接或间接公开Suit values[](如何通过getValues() 提及),我使用公共方法解决ordinal 值必须如何发送一个参数并从Suit values[] 返回Suit 表示。这里的重点(从一开始的问题图块)是从枚举序数创建一个枚举类型
    【解决方案3】:

    我同意大多数人的观点,即使用序数可能是个坏主意。我通常通过为枚举提供一个私有构造函数来解决这个问题,该构造函数可以采用例如 DB 值,然后创建一个类似于 Jan 答案中的静态 fromDbValue 函数。

    public enum ReportTypeEnum {
        R1(1),
        R2(2),
        R3(3),
        R4(4),
        R5(5),
        R6(6),
        R7(7),
        R8(8);
    
        private static Logger log = LoggerFactory.getLogger(ReportEnumType.class);  
        private static Map<Integer, ReportTypeEnum> lookup;
        private Integer dbValue;
    
        private ReportTypeEnum(Integer dbValue) {
            this.dbValue = dbValue;
        }
    
    
        static {
            try {
                ReportTypeEnum[] vals = ReportTypeEnum.values();
                lookup = new HashMap<Integer, ReportTypeEnum>(vals.length);
    
                for (ReportTypeEnum  rpt: vals)
                    lookup.put(rpt.getDbValue(), rpt);
             }
             catch (Exception e) {
                 // Careful, if any exception is thrown out of a static block, the class
                 // won't be initialized
                 log.error("Unexpected exception initializing " + ReportTypeEnum.class, e);
             }
        }
    
        public static ReportTypeEnum fromDbValue(Integer dbValue) {
            return lookup.get(dbValue);
        }
    
        public Integer getDbValue() {
            return this.dbValue;
        }
    
    }
    

    现在您可以在不更改查找的情况下更改顺序,反之亦然。

    【讨论】:

    • 这是正确的答案。与其他更直接但可能无效的答案相比,我很惊讶它得到了这么少的分数(由于未来的代码更改)。
    【解决方案4】:

    您可以定义一个简单的方法,例如:

    public enum Alphabet{
        A,B,C,D;
    
        public static Alphabet get(int index){
            return Alphabet.values()[index];
        }
    }
    

    并像这样使用它:

    System.out.println(Alphabet.get(2));
    

    【讨论】:

      【解决方案5】:

      因此,一种方法是使用ExampleEnum valueOfOrdinal = ExampleEnum.values()[ordinal];,这很有效而且很简单,但是, 如前所述,ExampleEnum.values() 为每次调用返回一个新的克隆数组。这可能是不必要的昂贵。我们可以通过像ExampleEnum[] values = values() 那样缓存数组来解决这个问题。允许修改我们的缓存数组也是“危险的”。有人可以写ExampleEnum.values[0] = ExampleEnum.type2; 所以我会使用不进行额外复制的访问器方法将其设为私有。

      private enum ExampleEnum{
          type0, type1, type2, type3;
          private static final ExampleEnum[] values = values();
          public static ExampleEnum value(int ord) {
              return values[ord];
          }
      }
      

      您将使用ExampleEnum.value(ordinal) 来获取与ordinal 关联的枚举值

      【讨论】:

        【解决方案6】:

        这就是我在 Android 上使用 Proguard 所做的:

        public enum SomeStatus {
            UNINITIALIZED, STATUS_1, RESERVED_1, STATUS_2, RESERVED_2, STATUS_3;//do not change order
        
            private static SomeStatus[] values = null;
            public static SomeStatus fromInteger(int i) {
                if(SomeStatus.values == null) {
                    SomeStatus.values = SomeStatus.values();
                }
                if (i < 0) return SomeStatus.values[0];
                if (i >= SomeStatus.values.length) return SomeStatus.values[0];
                return SomeStatus.values[i];
            }
        }
        

        它很短,我不用担心 Proguard 会出现异常

        【讨论】:

          【解决方案7】:
          public enum Suit implements java.io.Serializable, Comparable<Suit>{
            spades, hearts, diamonds, clubs;
            private static final Suit [] lookup  = Suit.values();
            public Suit fromOrdinal(int ordinal) {
              if(ordinal< 1 || ordinal> 3) return null;
              return lookup[value-1];
            }
          }
          

          测试类

          public class MainTest {
              public static void main(String[] args) {
                  Suit d3 = Suit.diamonds;
                  Suit d3Test = Suit.fromOrdinal(2);
                  if(d3.equals(d3Test)){
                      System.out.println("Susses");
                  }else System.out.println("Fails");
              }
          }
          

          如果您有更高效的代码,我很感激您与我们分享,我的枚举很大并且不断被调用数千次。

          【讨论】:

          • 我想你的意思是“if(ordinal 4) return null;”
          【解决方案8】:

          要将序数转换为其枚举表示,您可能需要这样做:

          ReportTypeEnum value = ReportTypeEnum.values()[ordinal];
          

          请注意数组边界。

          请注意,每次调用 values() 都会返回一个新克隆的数组,这可能会对性能产生负面影响。如果要经常调用它,您可能需要缓存该数组。

          Code example on how to cache values().


          编辑此答案以包含 cmets 中给出的反馈

          【讨论】:

          • 我实施了这个解决方案,但它对我不起作用。它返回的序数值不保证与添加枚举类型的顺序相匹配。我不知道这就是这个答案所倡导的,但我还是想警告人们
          • @IcesDante:序数肯定与源中枚举值的顺序相对应。如果您观察到不同的行为,那么肯定有其他问题。然而,由于其他答案中列出的所有原因,我上面的答案并不理想。
          • @JoachimSauer 也许 IcedD​​ante 意味着如果它是由早期版本的源生成和保存的序数可能不匹配,其中枚举值的顺序不同。
          【解决方案9】:

          这是我使用的。我不假装它比上面更简单的解决方案“效率”低得多。当上述解决方案中使用无效的序数值时,它的作用是提供比“ArrayIndexOutOfBounds”更清晰的异常消息。

          它利用了 EnumSet javadoc 指定迭代器以自然顺序返回元素的事实。如果不正确,则有一个断言。

          JUnit4 测试演示了它是如何使用的。

           /**
           * convert ordinal to Enum
           * @param clzz may not be null
           * @param ordinal
           * @return e with e.ordinal( ) == ordinal
           * @throws IllegalArgumentException if ordinal out of range
           */
          public static <E extends Enum<E> > E lookupEnum(Class<E> clzz, int ordinal) {
              EnumSet<E> set = EnumSet.allOf(clzz);
              if (ordinal < set.size()) {
                  Iterator<E> iter = set.iterator();
                  for (int i = 0; i < ordinal; i++) {
                      iter.next();
                  }
                  E rval = iter.next();
                  assert(rval.ordinal() == ordinal);
                  return rval;
              }
              throw new IllegalArgumentException("Invalid value " + ordinal + " for " + clzz.getName( ) + ", must be < " + set.size());
          }
          
          @Test
          public void lookupTest( ) {
              java.util.concurrent.TimeUnit tu = lookupEnum(TimeUnit.class, 3);
              System.out.println(tu);
          }
          

          【讨论】:

            【解决方案10】:

            每个枚举都有name(),它给出一个带有枚举成员名称的字符串。

            给定enum Suit{Heart, Spade, Club, Diamond}Suit.Heart.name() 将给Heart

            每个枚举都有一个valueOf()方法,它接受一个枚举类型和一个字符串,来执行反向操作:

            Enum.valueOf(Suit.class, "Heart") 返回Suit.Heart

            为什么有人会使用序数是我无法理解的。它可能快纳秒,但不安全,如果枚举成员发生变化,因为另一个开发人员可能不知道某些代码依赖于序数值(特别是在问题中引用的 JSP 页面中,网络和数据库开销完全占主导地位)时间,而不是在字符串上使用整数)。

            【讨论】:

            • 因为比较整数比比较字符串快很多?
            • 但是如果有人修改枚举(添加/重新排序 memvers),序数会改变。有时它是关于安全而不是速度,尤其是在一个 JSP 页面上,其中网络延迟是比较整数数组(字符串)和单个整数之间的差异的 1,000,000 倍。
            • toString 可以被覆盖,因此它可能不会返回枚举名称。方法名()是枚举名称(它是最终的)
            • code cmets和应用程序版本也是一个东西(可能文件格式这样更简单更小)
            • 不知道为什么当它基本上推荐与 oxbow_lakes 的答案相同的东西时,为什么会被否决。绝对比使用序数更安全。
            【解决方案11】:

            可以使用静态查找表:

            public enum Suit {
              spades, hearts, diamonds, clubs;
            
              private static final Map<Integer, Suit> lookup = new HashMap<Integer, Suit>();
            
              static{
                int ordinal = 0;
                for (Suit suit : EnumSet.allOf(Suit.class)) {
                  lookup.put(ordinal, suit);
                  ordinal+= 1;
                }
              }
            
              public Suit fromOrdinal(int ordinal) {
                return lookup.get(ordinal);
              }
            }
            

            【讨论】:

            • 另见Enums
            • 哇!哇!当然,这很简洁,但是...你知道 - 我内心的 C 程序员看到你分配了一个成熟的 HashMap 并在其中执行所有查找只是为了从本质上管理 4 个常量:黑桃、红心、钻石和俱乐部! C 程序员会为每个分配 1 个字节:'const char CLUBS=0;'等等... 是的,HashMap 查找是 O(1),但是 HashMap 的内存和 CPU 开销,在这种情况下,它比直接调用 .values() 慢很多数量级并且资源匮乏!如果人们这样写,难怪 Java 会如此占用内存......
            • 并非每个程序都需要 3A 游戏的性能。在许多情况下,为了类型安全、可读性、可维护性、跨平台支持、垃圾收集等而交换内存和 CPU……是合理的。高级语言的存在是有原因的。
            • 但是如果你的键范围总是0...(n-1),那么数组的代码更少,可读性也更好;性能提升只是一个奖励。 private static final Suit[] VALUES = values();public Suit fromOrdinal(int ordinal) { return VALUES[ordinal]; }。额外优势:在无效序数上立即崩溃,而不是静默返回 null。 (并不总是优势。但经常。)
            【解决方案12】:

            这几乎肯定是一个坏主意。当然,如果序号 de-facto 保持不变(例如,因为有人为 URL 添加了书签) - 这意味着您将来必须始终保留 enum 排序,这对于代码维护人员来说可能并不明显行。

            为什么不使用myEnumValue.name()enum 进行编码(并通过ReportTypeEnum.valueOf(s) 进行解码)?

            【讨论】:

            • 如果更改枚举的名称(但保持顺序)怎么办?
            • @Arne - 我认为这比一些没有经验的人出现并在开头或其正确的字母/逻辑位置添加value 的可能性要小得多。 (通过逻辑我的意思是例如 TimeUnit 值具有逻辑位置)
            • 我当然更喜欢强制枚举顺序而不是我的枚举名称...这就是为什么我更喜欢在数据库中存储序数而不是枚举名称的原因。此外,最好使用 int 操作而不是 String...
            • 我同意。在公共 API 中,更改 Enum 的名称会破坏向后兼容性,但更改顺序不会。出于这个原因,使用名称作为您的“键”更有意义
            • 存储序号可以更轻松地将您的想法翻译成其他语言。如果你需要用 C 编写一些组件怎么办?
            猜你喜欢
            • 2023-02-21
            • 1970-01-01
            • 1970-01-01
            • 2010-12-21
            • 1970-01-01
            • 2015-01-25
            • 1970-01-01
            相关资源
            最近更新 更多