在切换案例中使用Enum的序数值

问题描述 投票:10回答:7

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

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

注意:返回值仅是示例。

[不幸的是,Eclipse(我正在使用1.6 JDK)给出了我的编译错误”表达式必须是常量表达式”。我应该怎么做?除了静态查找表之外,还有其他方法可以在这里描述:Convert from enum ordinal to enum type吗?

java enums switch-statement
7个回答
19
投票

这是完成的,只要您在某个地方有某种顺序的序数。保持枚举的通常方法是按其名称而不是按序排列。同样,在正常情况下,您不应该使用序数,除非尝试实现类似EnumMap / Set的东西。当然,枚举可以只是来自C的东西的端口,并且处理不可避免的int,需要转换为Enum对象。

只需使用Enum.values()来获得由ordinal()排序的数组,因为每次都会克隆该数组,因此保持对它的引用是可以的。

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的行为。如果您很勇敢(并且枚举常量不超过64个),则可以使用java.util.EnumSet或简单的long。例如:

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

5
投票

首先,您不应该过多地依赖序数。如果可能,将变量设置为String(并使用enum转换为Enum.valueOf(string),或者最好将其设置为enum

如果确实不能,请使用enum.values()[ordinal]。然后使用开关中的枚举。


2
投票

答案的目标是@Riaan对常量vs方法枚举和性能原因的评论,它不能直接回答OP问题,因此我认为这可以视为噪音。但是,我认为了解内部工作原理很重要。

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

还有更多,该基准实际上是呼叫站点测试。呼叫站点的优化对于1而言是完全不同的,对于2而言,还有更多,甚至更多。呼叫站点是对抽象(或被重写)方法的调用。

下面是具有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热身...148 1%t1.ZEnums :: testEnum @ 7(125字节)150 1 t1.ZEnums $ MyEnum $ 6 :: getBooleanValue(2个字节)152 2 t1.ZEnums $ MyEnum $ 1 :: getBooleanValue(2个字节)154 3 t1.ZEnums $ MyEnum $ 2 :: getBooleanValue(2个字节)155 4 t1.ZEnums $ MyEnum $ 3 :: getBooleanValue(2个字节)158 2%t1.ZEnums :: testAlt @ 7(125字节)162 5 t1.ZEnums :: testEnum(125字节)164 6 t1.ZEnums :: testAlt(125个字节)21:08:46.716热身:160000021:08:46.716测试40000000次迭代21:08:46.716 ====21:08:46.716使用覆盖方法进行测试...21:08:47.513 替代方法版本花费781ms,长度:16000000021:08:47.513使用c-tor中的常数进行测试...21:08:48.138 c-tor版本的常数用了625ms,长度:160000000

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

然而,最有趣的部分是,当编译器开始使用内联高速缓存,然后使用该常量时,枚举常量将从2变为3,而WOW神奇的是,一切都会改变。


底线是:适当的基准测试确实很困难,并且涉及到一些知识,即JIT的编译方式,何时GC可能是个问题(要么删除要么接受)等等。链接

1
投票

只需使用枚举常量:

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

假设类似:

public enum MyEnum {
    A, B
}

但是要小心NullPointerException(如果variablenull


1
投票

您要的可能是:如果您需要在枚举本身的方法中进行切换:

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

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([email protected]


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的原因。

SOLUTION在到达交换条件之前将ENUM检查为NULL。

trafficLights != null
© www.soinside.com 2019 - 2024. All rights reserved.