繁体   English   中英

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

[英]Using Enum's ordinal value in switch-case statement

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

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

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

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

这就是如何完成的,前提是你在某个地方有一个序列化的序数。 保持枚举的通常方法是通过它的名称,而不是序数。 此外,除非尝试实现 EnumMap/Set 之类的东西,否则您不应在正常情况下使用序数。 当然,枚举可以只是一个来自 C 的端口,并且处理不可避免的 int,需要转换为枚举 object。

只需使用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:
...
   }
}

刚刚注意到你只需要 true 和 false,这是一种 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));
}

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

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

答案针对@Riaan 对常量与方法枚举和性能原因的评论,它不直接回答 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 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 神奇地改变了一切。


底线是:正确的基准测试确实很难,并且需要了解 JIT 如何编译、GC 何时可能成为问题(删除它或接受它)等等。
链接

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

枚举:

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

戴夫文柏宏 (manpakhong@hotmail.com)

只需使用枚举常量:

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

假设类似:

public enum MyEnum {
    A, B
}

但要小心NullPointerException (如果variablenull

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

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

在不同的 class 中:

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

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

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() function 中抛出 NPE,即使我们没有调用该方法。

解决方案是在 ENUM 到达开关盒之前检查 ENUM 的 NULL。

if (trafficLights != null) {
  switch (trafficLights) {
  case RED: {/* do stuff */}
  case YELLOW: {/* do stuff */}
  case GREEN: {/* do stuff */}
  }
} 

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM