从枚举序数转换为枚举类型


315

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

如何将序数转换为ReportTypeEnum

使用Java 6 SE。


1
到目前为止,还没有Java 6 EE(AFAIK)。目前的Java SE 6和Java EE 5
胡沙姆阿里

Answers:


632

要将序数转换为其枚举形式,您可能需要执行以下操作:

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

请注意数组边界。

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

有关如何缓存的代码示例values()


编辑了该答案,以包括评论中给出的反馈


我实施了此解决方案,但对我不起作用。它返回的序数值不能保证与枚举类型的添加顺序匹配。我不知道这是该答案所提倡的,但是我仍然想警告人们
IcedD​​ante 2015年

@IcesDante:当然可以保证序数与源中Enumeration值的顺序相对应。如果您观察到不同的行为,那么其他某些事情一定是错误的。但是,由于其他答案中列出的所有原因,我的上述答案并不理想。
约阿希姆·绍尔

137

这几乎肯定是一个坏主意。当然,如果序号事实上仍然存在(例如,由于某人已将URL标记为书签),则意味着您enum将来必须始终保留该顺序,这对于编写代码的维护人员而言可能并不明显。

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


24
如果更改枚举名称(但保持顺序)怎么办?
Arne Evertsson,2009年

6
@Arne-我认为这比一些没有经验的人value在开头或其正确的字母/逻辑位置加上a的可能性要小得多。(从逻辑TimeUnit
上讲

7
我当然更喜欢强制枚举顺序,而不是强制使用我的枚举的名称。这就是为什么我更喜欢在数据库中存储序数而不是枚举的名称。此外,最好使用int运算而不是String ...

8
我同意。在公共API中,更改枚举的名称将破坏向后兼容性,但更改顺序不会。因此,将名称用作您的“密钥”更为合理
Noel

3
存储序数可以更轻松地将您的想法翻译成其他语言。如果您需要用C编写某些组件怎么办?
QED 2015年

94

如果我要经常使用values()

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

同时在任何地方.java:

Suit suit = Suit.values[ordinal];

注意数组边界。


3
+1这是迄今为止最好的解决方案,恕我直言,因为尤其是在android.os.Message中,它可以传递序数。
likejudo

9
这非常令人担忧,因为数组是可变的。即使values []是最终的,它也不会阻止Suit.values[0] = Suit.Diamonds;代码中的某处。理想情况下,这永远不会发生,但是不公开可变字段的总体原则仍然成立。对于这种方法,请考虑使用Collections.unmodifiableList或类似方法。
Mshnik

怎么样-私有静态最终诉讼values [] = values(); public static Suit [] getValues(){返回值;}
Pratyush

4
@Pratyush可以使数组变量不可变,但不可变。我仍然可以做getValues()[0] = somethingElse;
卡拉巴辛'18

同意,因此要点不是Suit values[]直接或间接(如何提到getValues()),我使用一种公共方法来解决,在该方法中ordinal必须以参数的形式发送值并Suit从中返回表示形式Suit values[]。这里的要点(从一开始的问题开始就是根据枚举序数创建枚举类型的)
Manuel Jordan

13

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

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

}

现在,您可以更改顺序而无需更改查找,反之亦然。


这是正确的答案。与其他更直接但潜在的无效答案相比,我感到很惊讶(由于将来的代码更改)。
卡拉巴辛'18

8

可以使用静态查找表:

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

3
另请参阅枚举
垃圾桶2011年

11
哇!哇!当然,这很整洁,但是...您知道-我内心的C程序员痛苦地尖叫着,看到您分配了一个完整的HashMap并在其中进行查找,所有这一切实际上只需要管理4个常量:黑桃,心,钻石和俱乐部!AC程序员将为每个分配1个字节:'const char CLUBS = 0;' 等等...是的,HashMap查找为O(1),但是HashMap的内存和CPU开销在这种情况下比直接调用.values()慢了许多数量级,而且资源浪费!难怪如果人们这样写的话,Java就是这样的记忆猪吗?
Leszek,2015年

2
并非每个程序都需要三重A游戏的性能。在许多情况下,为了类型安全,可读性,可维护性,跨平台支持,垃圾回收等而对内存和CPU进行交易是合理的。存在高级语言是有原因的。
2015年

3
但是,如果您的键范围始终是0...(n-1),则数组的代码更少,可读性也更高;性能提升只是一个奖励。private static final Suit[] VALUES = values();public Suit fromOrdinal(int ordinal) { return VALUES[ordinal]; }。额外的优势:在无效序号上立即崩溃,而不是无提示地返回null。(并不总是有优势。但是经常如此。)
Thomas

4

这就是我用的。我不假装它比上面的简单解决方案“效率”低得多。当上面的解决方案中使用无效序数值时,它的作用是提供比“ 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);
}

1

这是我在带有Proguard的Android上执行的操作:

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中出现异常


1

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

public enum Alphabet{
    A,B,C,D;

    public static Alphabet get(int index){
        return Alphabet.values()[index];
    }
}

并像这样使用它:

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

0
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 <1 || ordinal> 4)返回null;”
geowar

0

因此,一种方法是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


-1

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

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

每个枚举都有一个valueOf()方法,该方法采用枚举类型和字符串来执行相反的操作:

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

为什么有人会使用普通话,这超出了我的范围。如果枚举成员更改,它可能快十亿分之一秒,但并不安全,因为另一个开发人员可能不知道某些代码依赖于序数值(尤其是在问题中引用的JSP页面中,网络和数据库开销完全支配了时间,而不是在字符串上使用整数)。


2
因为比较整数比比较字符串快得多?
HighCommander4

2
但是,如果有人修改枚举(添加/重新排序记忆),则常规将改变。有时,这是关于安全而不是速度的问题,尤其是在JSP页面上,该页面的网络延迟是比较整数数组(字符串)和单个整数之间的差值的1,000,000倍。
Tony BenBrahim

toString可以被覆盖,因此它可能不返回枚举名称。方法名称()是给出枚举名称的方法(它是最终的)
gerardw 2014年

代码注释和应用程序版本也是一回事(也许文件格式更简单,更小)
joe pasteier

不确定在基本上建议与oxbow_lakes的答案相同的事情时,为什么要对此予以否决。比使用序数绝对安全。
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.