关于java:从枚举序号转换为枚举类型

Convert from enum ordinal to enum type

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

如何将序数转换为ReportTypeEnum

使用Java 6 SE。


要将序数转换为其枚举重新表示,您可能需要执行以下操作:

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

请注意数组边界。

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

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

编辑此答案以包括评论中给出的反馈


这几乎肯定是个坏主意。当然,如果序数事实上是持久的(例如,因为有人在URL上加了书签),这意味着您以后必须始终保留enum的顺序,这对于代码维护人员来说可能并不明显。

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


如果我要经常使用values()

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

同时where.java:

1
Suit suit = Suit.values[ordinal];

注意阵列边界。


我同意大多数人的看法,使用序数可能是一个坏主意。我通常通过给枚举一个私有的构造函数来解决这个问题,该构造函数可以例如一个db值,然后创建一个与jan答案中的函数类似的静态fromDbValue函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public 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;
    }

}

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


您可以使用静态查阅表格:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);
  }
}


这是我用的。我并没有假装它远不如上述简单的解决方案"高效"。它所做的是提供比"arrayindexoutofbounds"更清晰的异常消息,因为在上面的解决方案中使用了无效的序号。

它利用了这样一个事实:Enumset JavaDoc以自然顺序指定迭代器返回元素。如果这不正确,就有断言。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 /**
 * 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);
}


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

1
2
3
4
5
6
7
8
9
10
11
12
13
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
2
3
4
5
6
7
public enum Alphabet{
    A,B,C,D;

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

使用方法如下:

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

所以有一种方法是doExampleEnum valueOfOrdinal = ExampleEnum.values()[ordinal];,它工作起来很容易,但是,如前所述,ExampleEnum.values()为每个调用返回一个新的克隆数组。那可能是不必要的昂贵。我们可以通过缓存类似于ExampleEnum[] values = values()的数组来解决这个问题。允许修改缓存数组也是"危险的"。有人可以编写ExampleEnum.values[0] = ExampleEnum.type2;,这样我就可以使用不做额外复制的访问器方法将其私有化。

1
2
3
4
5
6
7
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
2
3
4
5
6
7
8
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];
  }
}

测试类

1
2
3
4
5
6
7
8
9
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");
    }
}

我很感谢您与我们分享,如果您有一个更有效的代码,我的枚举是巨大的,经常被调用数千次。


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

鉴于enum Suit{Heart, Spade, Club, Diamond}Suit.Heart.name()将给予Heart

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

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

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