在switch-case语句中使用Enum的序数值

时间:2011-07-23 08:18:50

标签: java enums switch-statement

对于我的项目,我正在使用枚举,我需要实现switch-case语句,其中检查特定Enum的序数值,如下所示:

        switch ( variable )
        {
        case MyEnum.A.ordinal():
            return true;
        case MyEnum.B.ordinal():
            return true;
        default:
            return false;
        }

注意:返回值只是一个示例。

不幸的是,Eclipse(我使用的是1.6 JDK)给出了我的编译错误“case 表达式必须是常量表达式“。我应该怎么做?除了静态查找表之外是否还有其他方法,如下所述:Convert from enum ordinal to enum type

7 个答案:

答案 0 :(得分:18)

这是怎么做的,前提是你有某种序列化顺序。通常,坚持枚举的方式是它的名字,而不是顺序。除非尝试实现类似EnumMap / Set之类的东西,否则你不应该在正常情况下使用序数。当然,枚举可以只是来自C类似东西的端口,处理不可避免的int,需要转换为Enum对象。

只需使用Enum.values()来获取ordinal()排序的数组,因为每次都克隆数组,保持ref是正确的。

enum E{
 A, B, C...   
}

final static E[] vals = E.values();//copy the values(), calling values() clones the array
boolean f(int variable){
  switch(vals[variable]){
  case A:
...
  case B:
...
//break;
  default:
...
   }
}

注意到你只需要真假,这是一种Set行为。你可以使用java.util.EnumSet或简单的long,如果感觉很勇敢(并且没有超过64个枚举常量)。例如:

private static <E extends Enum> long ord(E e){
  return 1L<<e.ordinal();
}

static final long positiveSet = ord(E.A)+ord(E.B);
boolean f(int ordinal){
  return 0!=(positiveSet&(1L<<ordinal));
}

答案 1 :(得分:4)

首先,你不应该依赖那么多的序数。如果可能,请将变量设为String(并使用enum转换为Enum.valueOf(string),或者最多使其变为enum

如果你真的不能,那就使用enum.values()[ordinal]。然后在开关中使用枚举。

答案 2 :(得分:2)

答案针对的是@Riaan对常量vs方法枚举和性能原因的评论,并没有直接回答OP问题,所以我认为它可以被认为是噪音。 但是,我认为理解内部运作方式是一件很重要的事情。

我从他的示例中取出了基准并对其进行了改进,以删除占用90%执行时间的垃圾收集和字符串创建。添加了预热阶段以确保热点实际编译方法。

还有一些,基准是有效的callsite测试。对于呼叫者来说,优化对于1来说是完全不同的,对于少数几个以及对于更多的人而言是2。 callsite是对abstract(或者只是重写)方法的调用。

以下是6个枚举常量的测试:

package t1;

public class ZEnums {

    public enum MyEnum {
  A { boolean getBooleanValue(){ return true; }},
  B { boolean getBooleanValue(){ return true; }},
  C { boolean getBooleanValue(){ return false; }},
  D { boolean getBooleanValue(){ return false; }},
  E { boolean getBooleanValue(){ return false; }},
  F { boolean getBooleanValue(){ return false; }}, 

  ;
  abstract boolean getBooleanValue();
  }

  public enum MyEnumAlt {
    A (true), 
    B (true),
    C (false),
    D (false),
    E (false),
    F (false),
    ;
    private final boolean isTrue;
    MyEnumAlt( boolean isTrue){ this.isTrue = isTrue; }
    boolean getBooleanValue(){ return isTrue; };
  }

  public static void main(String[] args) {
    log("Warming up...");
    //10k iterations won't do since not all paths for MyEnum are invoked 10k (default) times to warrant compilations 
    long warmum = testEnum(100000 )+ testAlt(100000)+testEnum(100000 )+ testAlt(100000);
    log("Warm up: %d", warmum);     
    //no info from +XX:+PrintCompilation below this one, or the test is invalid
    testMain();

    }
    public static void testMain() {
        int iterations = (int)4e7;

        log("Testing %d iterations%n", iterations);
        log("====");

        log("Testing with Overridden method...");       
    System.gc();
    {
    long start = System.currentTimeMillis();
    long len = 0;
    len = testEnum(iterations);
    long time = System.currentTimeMillis()-start;
    log("Overridden method version took %dms, length: %d ", time, len);
    }
////////////
    System.gc();
    {
    log("Testing with Constant in c-tor... ");
    long start = System.currentTimeMillis();
    long len = testAlt(iterations);

    long time = System.currentTimeMillis()-start;
    log("Constant in c-tor version took %dms, length: %d ", time, len);
    }
    }
    private static long testEnum(int iterations) {
        long len = 0;
        for(int i=0; i<iterations; i++){
        MyEnum tmpEnum = MyEnum.A;
        if(i%3==0){ tmpEnum = MyEnum.A;        
        }else if(i%4==0){ tmpEnum = MyEnum.B;
        }else if(i%5==0){ tmpEnum = MyEnum.C;
        }else if(i%6==0){ tmpEnum = MyEnum.D;
        }else if(i%6==0){ tmpEnum = MyEnum.E;
        }else{ tmpEnum = MyEnum.F; 
        }
        String tmp = tmpEnum.getBooleanValue()?"XXX":"ABCDE";
        len+=tmp.length();
    }
        return len;
    }
    private static long testAlt(int iterations) {
        long len =0;
        for(int i=0; i<iterations; i++){
        MyEnumAlt tmpEnum = MyEnumAlt.A;
        if(i%3==0){ tmpEnum = MyEnumAlt.A;
        }else if(i%4==0){ tmpEnum = MyEnumAlt.B;
        }else if(i%5==0){ tmpEnum = MyEnumAlt.C;
        }else if(i%6==0){ tmpEnum = MyEnumAlt.D;
        }else if(i%6==0){ tmpEnum = MyEnumAlt.E;
        }else{ tmpEnum = MyEnumAlt.F; 
        }
        String tmp = tmpEnum.getBooleanValue()?"XXX":"ABCDE";
        len+=tmp.length();
    }
        return len;
    }
    static void log(String msg, Object... params){ 
        String s = params.length>0?String.format(msg, params):msg;
        System.out.printf("%tH:%<tM:%<tS.%<tL %s%n", new Long(System.currentTimeMillis()), s);
    }
}
21:08:46.685 Warming up...
    148   1%      t1.ZEnums::testEnum @ 7 (125 bytes)
    150   1       t1.ZEnums$MyEnum$6::getBooleanValue (2 bytes)
    152   2       t1.ZEnums$MyEnum$1::getBooleanValue (2 bytes)
    154   3       t1.ZEnums$MyEnum$2::getBooleanValue (2 bytes)
    155   4       t1.ZEnums$MyEnum$3::getBooleanValue (2 bytes)
    158   2%      t1.ZEnums::testAlt @ 7 (125 bytes)
    162   5       t1.ZEnums::testEnum (125 bytes)
    164   6       t1.ZEnums::testAlt (125 bytes)
21:08:46.716 Warm up: 1600000
21:08:46.716 Testing 40000000 iterations

21:08:46.716 ====
21:08:46.716 Testing with Overridden method...
21:08:47.513 Overridden method version took 781ms, length: 160000000 
21:08:47.513 Testing with Constant in c-tor... 
21:08:48.138 Constant in c-tor version took 625ms, length: 160000000 

代码运行时带有-server -XX:+PrintCompilation个选项。 当然,差异并不大。然而,这不是一个有趣的问题。如果您测试具有2个枚举常量的版本,结果可能会有很大不同。对于2个调用站点,编译器通过嵌入相关方法来生成代码。在上面的测试中,将删除对booleanValue的整个调用,甚至可以在O(1)中执行测试。

然而,当编译器开始使用内联缓存然后是常量时,最有趣的部分是2到3个枚举常量,而WOW魔法则会改变所有内容。

<小时/> 底线是:正确的基准测试真的很难,并且当GC可能是一个问题(移除它或接受它)等时,需要了解JIT如何编译的一些知识。
链接

答案 3 :(得分:1)

只使用枚举常量:

MyEnum variable;
...
switch ( variable ) {
    case A:
        return true;
    case B:
        return true;
    default:
        return false;
}

假设类似:

public enum MyEnum {
    A, B
}

但要注意NullPointerException(如果variablenull

答案 4 :(得分:1)

你要求的可能是: 如果你需要enum本身的方法中的开关:

switch ( this )
        {
        case A:
            return true;
        case B:
            return true;
        default:
            return false;
        }

在另一个班级:

switch ( variable )  //Variable of type myEnum
        {
        case A:
            return true;
        case B:
            return true;
        default:
            return false;
        }

如果添加另一个枚举,很容易忘记更新switch语句,所以更好的办法是将这样的方法放在枚举本身并使用特定于常量的方法实现:

public enum MyEnum
    A { boolean getBooleanValue(){ return true; },
    B { boolean getBooleanValue(){ return true; },
    C { boolean getBooleanValue(){ return false; };
    abstract boolean getBooleanValue();
}

这样,如果您添加新的枚举值,编译器将提醒您声明getBooleanValue方法,并且只需在需要的地方使用A.getBooleanValue();

正如评论中指出的那样,另一种选择是:

public enum MyEnumAlt {
    A (true), 
    B (true),
    C (false);
    private final boolean isTrue;
    MyEnumAlt( boolean isTrue){ this.isTrue = isTrue; }
    boolean getBooleanValue(){ return isTrue; };
}

这是一个偏好问题,具体情况会有所不同。如果你只是为每个枚举返回一个值,那么构造函数版本似乎是合理的,但我发现它的可读性较差。通过测试可以看出对此表现更好的担忧是没有根据的:

public void testMain() {
        System.out.println("Testing with constructor: ");
        long start = System.currentTimeMillis();
        for(int i=0; i<1000*1000; i++){
            MyEnum tmpEnum = null;
            if(i%3==0){ tmpEnum = MyEnum.A;
            }else if(i%4==0){ tmpEnum = MyEnum.B;
            }else{ tmpEnum = MyEnum.C; }
            String tmp = Integer.toString(i)+" "+tmpEnum.getBooleanValue();
        }
        long time = System.currentTimeMillis()-start;
        System.out.println("Constructor version took "+time);

        System.out.println("Testing with Constant specific method implementation: ");
        long start2 = System.currentTimeMillis();
        for(int i=0; i<1000*1000; i++){
            MyEnumAlt tmpEnum2 = null;
            if(i%3==0){ tmpEnum2 = MyEnumAlt.A;
            }else if(i%4==0){ tmpEnum2 = MyEnumAlt.B;
            }else{ tmpEnum2 = MyEnumAlt.C; }
            String tmp2 = Integer.toString(i)+" "+tmpEnum2.getBooleanValue();
        }
        long time2 = System.currentTimeMillis()-start2;
        System.out.println("Constant specific method version took "+time2);
    }

答案 5 :(得分:1)

更好的解决方案是这样的:

枚举:

public interface ACServices {

    public static enum MessageType {

        // periodic needs to saved in DB
        PIPE_INFO_TYPE_AC_DEVICE_LIST, // periodic from littlecloud
        PIPE_INFO_TYPE_DEV_ONLINE,
        PIPE_INFO_TYPE_DEV_OFFLINE,
        PIPE_INFO_TYPE_EVENT_LOG,
        PIPE_INFO_TYPE_DEV_DETAIL,
    };

实现:

ACServices.MessageType msgType = ACServices.MessageType.valueOf(acResponse.getType());
switch (msgType){
    case INT_INFO_DEV_STATUS:
        break;
    case INT_INFO_DEV_TZ:
        break;
    case PIPE_INFO_DEV_COUNT:
        break;
    case PIPE_INFO_TYPE_AC_DEVICE_LIST:
        break;
    case PIPE_INFO_TYPE_CONFIG_GET_TEXT:
        break;
    default:
        break;
}

Man Pak Hong,Dave(manpakhong@hotmail.com)

答案 6 :(得分:0)

这是因为编译器看到了差异。例如下面的枚举代码,我们可以看到:

public enum TrafficLight {RED, YELLOW, GREEN}

TrafficLight trafficLights = ...
switch (trafficLights) {
  case RED: {/* do stuff */}
  case YELLOW: {/* do stuff */}
  case GREEN: {/* do stuff */}
}

但编译器请参见:

switch (trafficLights.ordinal()) {
      case 0: {/* do stuff */}
      case 1: {/* do stuff */}
      case 2: {/* do stuff */}
    }

这就是为什么当trafficlights为NULL时会抛出NPE的原因,即使我们没有调用该方法,您也不知道为什么在ordinal()函数中会抛出NPE。

解决方案在到达交换条件之前将ENUM检查为空。

trafficLights != null