12

For 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 ?

Community
  • 1
  • 1
DoktorNo
  • 269
  • 1
  • 5
  • 16
  • 2
    I guess I don't understand why you have to use the ordinal. Why can't you just switch on the enum itself? – emory Jul 23 '11 at 08:21
  • Just curious, but why are you using enums if you then have variables that use the ordinal values? The point of enums is their type safety, but your variable is not going to be type safe.. Anyway, it might be as simple as making the ordinal value a public constant attribute instead of accessing it via a getter method. – ChrisC Jul 23 '11 at 08:25
  • @emory Because I need ordinal value - enum's values are not useful for that particular purpose, because they are unrelated to outcome of switch-statement. – DoktorNo Jul 23 '11 at 08:28
  • 1
    I must admit that this makes no sense to me - How can the enum's value be unrelated to the outcome of the switch value? The ordinal is just an index to the enum values (an index that can change if you add a new enum value) - The ordinal by it's nature is nothing more than a transient property of the enum. – Riaan Cornelius Jul 23 '11 at 09:05
  • @DoktorNo, by way what you ask is EnumSet – bestsss Jul 23 '11 at 09:17
  • The enum member can be saved in a database (its ordinal value), in which case this is needed. – Buffalo Jun 11 '13 at 10:01

7 Answers7

21

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));
}
bestsss
  • 11,796
  • 3
  • 53
  • 63
5

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.

Bozho
  • 588,226
  • 146
  • 1,060
  • 1,140
  • 1
    iterating is bad in this case, just enum.values()[variable] is what you need. Since `values()` clones the array, you'd be better off having it in a static variable. – bestsss Jul 23 '11 at 09:05
  • you're welcome, my point is that, iterating totally defeats the purpose of case/switch statement that has performance in mind. – bestsss Jul 23 '11 at 09:14
  • agreed. I added it as a last-option (although it looks like the OP wants that) – Bozho Jul 23 '11 at 09:19
2

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:
bestsss
  • 11,796
  • 3
  • 53
  • 63
1

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)

blalasaadri
  • 5,990
  • 5
  • 38
  • 58
1

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)

user85421
  • 28,957
  • 10
  • 64
  • 87
1

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);
    }
Riaan Cornelius
  • 1,933
  • 1
  • 14
  • 17
  • Of course, you need to keep in mind that switching on enum values are not the best idea since it's not easy to maintain this code. If you add a new enum value, you need to remember to update all the switch statements... – Riaan Cornelius Jul 23 '11 at 08:54
  • 1
    adding functions like that in the Enum is counterproductive. 1st: you'd be better off having a separate boolean field and c-tor that initializes the enum (looks times better and it's better performance wise - both memory/time, each constant like this is a new class); 2nd adding more cases (functions) that depend on the enum involves changing the domain model, for example if you need another function that returns boolean. The guy just needs an EnumSet. – bestsss Jul 23 '11 at 09:27
  • It would depend on the situation. If it is as simple as returning a specific value, then yes, you could just do a constructor and field, but it would look very similar to this in any case. Regarding performance, There's only ever one instance of each enum field and the hotspot compiler will end up in-lining most of the code, so I would argue that this code would be better than trying to code the functionality in a method outside the enum. The performance difference in either case would be so minor that it won't make a difference for 99.9% of cases in any case. – Riaan Cornelius Jul 23 '11 at 12:32
  • And in fact, turns out I'm right. Test that allocates a million enums and builds a string out of them in a loop (that runs a million times): Constructor version took 268 ms Constant specific method version took 232 ms – Riaan Cornelius Jul 23 '11 at 12:47
  • 1
    I'm very much afraid the benchmark is sort yet-another micro-bencmark-not-got-right. The benchmark needs warmup, since the way it's done it includes the compilation time. Also the 2nd test is likely to include some garbage collection. btw, `MyEnum` cannot be compiled. I did rewrite the benchmark, the constants is slightly faster on 40M elements, the benchmarks spends most if the time in modulus and the String creation, so need very high number to have any effect. – bestsss Jul 23 '11 at 17:28
  • 1
    Not to sound offensive even the industry-standard benchmarks are often off-the mark: http://www.azulsystems.com/events/javaone_2002/microbenchmarks.pdf – bestsss Jul 23 '11 at 17:29
0

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 */}
  }
} 
Vlad K
  • 3
  • 2
Khang Tran
  • 467
  • 5
  • 16