Enable EnumSet

The old way, if we want to switch on some complex bitmask, we could easily do it like this (a random example from the top of my head to demonstrate the problem):

 private static final int MAN = 0x00000001; private static final int WOMAN = 0x00000002; // ...alive, hungry, blind, etc. private static final int DEAD = 0xFF000000; public void doStuff(int human) { switch (human) { case MAN | DEAD: // do something break; // more common cases } } 

Currently, since we use enums and EnumSets , I would sometimes like to do a similar thing:

 enum Human { MAN, WOMAN, DEAD; // etc. } public void doStuff(EnumSet human) { switch (human) { case Human.MAN | Human.DEAD: // do something break; // more common cases } } 

which does not work, because we can only switch on the value of int , enum or String . At this point, I realized that this is not possible, although enum values ​​are basically just hidden integers. But I like to dig around, and the function looks very useful, so:

 private static final EnumSet<Human> DEAD_MAN = EnumSet.of(Human.MAN, Human.DEAD); public void doStuff(EnumSet human) { switch (human) { case DEAD_MAN: // do something break; // more common cases } } 

Still out of luck. Knowing the trick to enable Strings and that EnumSets are actually 64-bit fields (or arrays of them), I would also try:

  switch (human.hashCode()) { case (Human.MAN.hashCode() | Human.DEAD.hashCode()): // do something break; // more common cases } 

thinking that when Human hashCode() is correctly implemented to give consistent results, it can work. Nope

java.lang.Error: Unresolved compilation problem: case expressions must be constant expressions


Now I wonder why there is no way to do this. I always thought of enums and EnumSets in Java as the correct replacement for these old-school bit fields, but here it seems that the new methods cannot handle more complex cases.

The correct kind of solution sucks compared to any of the switch features:

 public void doStuff(EnumSet human) { if (human.contains(Human.MAN) && human.contains(Human.DEAD)) { // do something } else { // more common cases } } 

In particular, since the introduction of switch on Strings , I believe that there are at least two possible implementations of switch on EnumSets :

  • In case (Human.MAN | Human.DEAD) expressions case (Human.MAN | Human.DEAD) simply use compile-time type checking and ordinal() instead of the enumerations themselves.
  • Using the same trick as for strings .
    • At the time of compilation, calculate hashCode() the name value of the enumeration values ​​(and, possibly, something extra - the number of values ​​in the enumeration, ordinal() , etc.), all static and constant from compilation time). Yes, that would mean changing the hashCode() any of the EnumSet or enum classes.
    • instead of the enumerations themselves

Now, is there any serious obstacle that I did not take into account (I can come up with a few, everything can be easily overcome) that would make this impossible to implement? Or am I right that this is really possible, but not enough for Oracle to implement it, because it is not used so often?


In addition, let me say that this is a purely academic question, perhaps without a good answer (I don’t know, I would not ask otherwise). I can make this community wiki if it proves incontrovertible. However, I could not find the answer (or even anyone discussing it) anywhere, so here it is.

+6
source share
3 answers

How to use Set EnumSet methods.

 private static final EnumSet<Human> DEAD_MAN = EnumSet.of(Human.MAN, Human.DEAD); public void doStuff(EnumSet human) { if ( human.containsAll( DEAD_MAN ) ) { // do something break; } else { // more common cases } } 

The Acutally EnumSet implementation of the Set interface methods is very efficient, and underneath it uses bitfield comparison.

+3
source

In Java and an object-oriented world, you will have a class with setters and getters on Object, and you will use those

 public void doStuff(Human human) { if(human.isDead()) { if(human.isMale()) { // something } else if (human.isFemale()) { // something else } else { // neither } } } 

Note. A switch is not a good idea because it only accepts exact matches. for example case MAN | DEAD: case MAN | DEAD: will not match MAN | HUNGRY | DEAD MAN | HUNGRY | DEAD MAN | HUNGRY | DEAD if you do not want to only match those who are not hungry before they die .;)


I will see your “absolutely sufficient” benchmark and I will raise you another erroneous test that “shows” that this requires a small part of the clock cycle (due to the fact that you are interested, it's hard to believe)

 public static void main(String... args) { Human human = new Human(); human.setMale(true); human.setDead(true); for(int i=0;i<5;i++) { long start = System.nanoTime(); int runs = 100000000; for(int j=0;j< runs;j++) doStuff(human); long time = System.nanoTime() - start; System.out.printf("The average time to doStuff was %.3f ns%n", (double) time / runs); } } public static void doStuff(Human human) { if (human.isDead()) { if (human.isMale()) { // something } else if (human.isFemale()) { // something else } else { // neither } } } static class Human { private boolean dead; private boolean male; private boolean female; public boolean isDead() { return dead; } public boolean isMale() { return male; } public boolean isFemale() { return female; } public void setDead(boolean dead) { this.dead = dead; } public void setMale(boolean male) { this.male = male; } public void setFemale(boolean female) { this.female = female; } } 

prints

 The average time to doStuff was 0.031 ns The average time to doStuff was 0.026 ns The average time to doStuff was 0.000 ns The average time to doStuff was 0.000 ns The average time to doStuff was 0.000 ns 

This is a 0.1 clock cycle on my machine before you completely disable it.

+6
source

Do the following (based on your example):

 enum Human { MAN, WOMAN, DEAD; // etc. } public void doStuff(Human human) { switch (human) { case MAN: case DEAD: // do something break; // more common cases } } 

If you want an EnumSet , then you cannot use switch and must reorganize it to if

 public void doStuff(EnumSet<Human> human) { if( human.containsAll(EnumSet.<Human>of(Human.MAN, Human.DEAD) { // do something } } 

The last option will do a bitwise comparison inside.

-4
source

All Articles