Using Enum's ordinal value in switch-case statement
F开发者_如何转开发or my project I am using enums, and I need to implement the switch-case statement, where ordinal numbers of values of the specific Enum are checked, like this way:
switch ( variable )
{
case MyEnum.A.ordinal():
return true;
case MyEnum.B.ordinal():
return true;
default:
return false;
}
Note: return values are only an example.
Unfortunately, Eclipse (I'm using 1.6 JDK) gives my compilation error "case expressions must be constant expressions". What I should do? Is there any other method than static lookup table, described here: Convert from enum ordinal to enum type ?
This is how is done, provided you have a serialized ordinal somehow, somewhere. Usual way to persist an enum is by its name, not ordinal, though. Also you should not use ordinal in normal circumstances unless trying to implement something like EnumMap/Set. Of course, the enum can be just a port from C alike stuff and dealing with the inevitable int, needs a transform to the Enum object.
Just use Enum.values()
to obtain an array ordered by ordinal()
, since the array is cloned each time, keeping a ref towards is ok.
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:
...
}
}
Just noticed you need only true and false, that's a Set type of behavior. You can use java.util.EnumSet or a simple long
, if feeling brave (and not having more than 64 enum constants). for example:
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));
}
First of all, you should not rely on the ordinal that much. If possible make your variable a String
(and transform to enum
using Enum.valueOf(string)
or at best make it enum
.
If you really can't, then use enum.values()[ordinal]
.Then use the enum in the switch.
The answer targets @Riaan comment on constant vs method enums and performance reasons and it doesn't directly answers the OP question, so it can be considered noise I suppose. However, I believe it's an important matter to understand how the inner workings.
I took the benchmark off his example and improved it to remove the garbage collection and string creation that takes over 90% of the execution time. Added warm up phase to ensure hotspot actually compiles the methods.
There is some more, the benchmark is effectively callsite test. The optimization for callsites are quite different for 1, for 2 for few more and for more-more. A callsite is an invocation of abstract (or just overridden) method.
Below is the test with 6 enums constants:
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
The code was run w/ -server -XX:+PrintCompilation
options.
The difference ain't huge, of course. However that's not the interesting issue. If you test the version with 2 enum constants though, the result can be significantly different. For 2 call sites the compiler generates the code by inlinining the method in question. In the test above that would remove entire the call to booleanValue and can even make execute the test in O(1).
The funniest part however is going from 2 to 3 enum constants when the compiler starts using inline caches and then the constant, and WOW magic the everything changes.
The bottom line is: proper benchmark is truly hard and involves some knowledge how the JIT compiles, when the GC might be an issue (either remove it or embrace it) and so on.
Links:
- Benchmakrs on JavaOne
- How NOT To Write A Microbenchmark
just use the enum constants:
MyEnum variable;
...
switch ( variable ) {
case A:
return true;
case B:
return true;
default:
return false;
}
assuming something like:
public enum MyEnum {
A, B
}
but beware of NullPointerException
(if variable
is null
)
What you are asking for is probably: If you need the switch in a method in the enum itself:
switch ( this )
{
case A:
return true;
case B:
return true;
default:
return false;
}
And in a different class:
switch ( variable ) //Variable of type myEnum
{
case A:
return true;
case B:
return true;
default:
return false;
}
It's easy to forget to update the switch statements if you add another enum though, so a better bet would be to put methods like this in the enum itself and use constant-specific method implementations:
public enum MyEnum
A { boolean getBooleanValue(){ return true; },
B { boolean getBooleanValue(){ return true; },
C { boolean getBooleanValue(){ return false; };
abstract boolean getBooleanValue();
}
This way, if you ever add a new enum value, the compiler will remind you to declare the getBooleanValue method and you just use A.getBooleanValue();
wherever you need it.
As was pointed out in the comments, another option is this:
public enum MyEnumAlt {
A (true),
B (true),
C (false);
private final boolean isTrue;
MyEnumAlt( boolean isTrue){ this.isTrue = isTrue; }
boolean getBooleanValue(){ return isTrue; };
}
It's a matter of preference and will vary on the specific situation. If you are simply returning a value for each enum, the constructor version is plausible, but I find it less readable. Concerns over this performing better is unfounded as you can see by testing it:
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);
}
A better solution would be something like this:
enum:
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,
};
implementation:
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)
It is because the compiler sees differences. For example this enum code, we can see:
public enum TrafficLight {RED, YELLOW, GREEN}
TrafficLight trafficLights = ...
switch (trafficLights) {
case RED: {/* do stuff */}
case YELLOW: {/* do stuff */}
case GREEN: {/* do stuff */}
}
BUT the compiler see:
switch (trafficLights.ordinal()) {
case 0: {/* do stuff */}
case 1: {/* do stuff */}
case 2: {/* do stuff */}
}
That's why it throws NPE when trafficLights are NULL and you also don't know why it throws NPE in the ordinal() function even though we didn't call that method.
SOLUTION is checking NULL the ENUM before it reaches to the switch-case.
if (trafficLights != null) {
switch (trafficLights) {
case RED: {/* do stuff */}
case YELLOW: {/* do stuff */}
case GREEN: {/* do stuff */}
}
}
精彩评论