Включить EnumSet
По-старому, если бы мы хотели switch
на некоторой сложной битовой маске мы могли бы легко сделать это так (случайный пример из головы, просто чтобы продемонстрировать проблему):
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
}
}
В наше время, так как мы используем enums
а также EnumSets
Я иногда хотел бы сделать подобное:
enum Human {
MAN, WOMAN, DEAD; // etc.
}
public void doStuff(EnumSet human) {
switch (human) {
case Human.MAN | Human.DEAD:
// do something
break;
// more common cases
}
}
который не работает, потому что мы можем только switch
на int
, enum
или же String
значение. На данный момент, я понял, что это не может быть сделано, хотя это enum
значения в основном просто скрытые целые числа. Но мне нравится копаться, и эта функция выглядит очень полезной, поэтому:
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
}
}
Все еще не повезло. Зная хитрость переключения Strings и то, что EnumSets на самом деле являются 64-битными полями (или их массивами), я бы также попробовал:
switch (human.hashCode()) {
case (Human.MAN.hashCode() | Human.DEAD.hashCode()):
// do something
break;
// more common cases
}
думая, что когда Human
hashCode()
будет правильно реализовано, чтобы дать последовательные результаты, это может работать. Нету:
java.lang.Error: Нерешенная проблема компиляции: выражения case должны быть константными выражениями
Теперь мне интересно, почему нет возможности сделать это. Я всегда думал о enums
а также EnumSets
в Java, как правильная замена для этих битовых полей старой школы, но здесь кажется, что новые способы не могут обрабатывать более сложные случаи.
Правильное решение отстой по сравнению с любым из switch
возможности:
public void doStuff(EnumSet human) {
if (human.contains(Human.MAN) && human.contains(Human.DEAD)) {
// do something
} else {
// more common cases
}
}
В частности, с момента введения switch
на Strings
Я считаю, что есть как минимум две возможные реализации switch
на EnumSets
:
- в
case (Human.MAN | Human.DEAD)
выражения, просто используйте проверку типа во время компиляции иordinal()
вместо самих перечислений. - Используя тот же трюк, что и для строк.
- Во время компиляции вычислите
hashCode()
изname
значений перечисления (и, возможно, что-то дополнительное - количество значений в перечислении,ordinal()
и т.д. - все статично и постоянно с момента компиляции). Да, это означало бы изменитьhashCode()
любой изEnumSet
класс илиEnum
учебный класс. - использовать вместо самих перечислений
- Во время компиляции вычислите
Теперь, есть ли какое-то серьезное препятствие, которое я не учел (я могу придумать несколько, все можно легко преодолеть), которое сделало бы это невозможным для реализации легко? Или я прав, что это действительно возможно, но недостаточно для того, чтобы Or acle его реализовал, потому что он не используется так часто?
Кроме того, позвольте мне заявить, что это чисто академический вопрос, возможно, без хорошего ответа (не знаю, я бы не стал спрашивать иначе). Я мог бы сделать это вики-сообществом, если это окажется неопровержимым. Тем не менее, я нигде не мог найти ответ (или даже любой, кто его обсуждает), так что вот так.
3 ответа
Как насчет использования Set
методы EnumSet
,
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
}
}
Реально реализация EnumSet Set
Методы интерфейса очень эффективны, и под ними находится сравнение битовых полей, которое вы ищете.
В Java и объектно-ориентированном мире у вас будет класс с сеттерами и геттерами для объекта, и вы будете использовать те
public void doStuff(Human human) {
if(human.isDead()) {
if(human.isMale()) {
// something
} else if (human.isFemale()) {
// something else
} else {
// neither
}
}
}
Примечание: переключатель не очень хорошая идея, потому что он принимает только точные совпадения. например case MAN | DEAD:
не будет соответствовать MAN | HUNGRY | DEAD
если только вы не хотите соответствовать тем, кто не был голоден, прежде чем они умерли.;)
Я увижу ваш "абсолютно достаточный" эталонный тест и предложу вам еще один некорректный эталонный тест, который "показывает", что он занимает долю такта (потому что вам интересно, в это трудно поверить)
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;
}
}
печать
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
Это 0,1 такта на моей машине, прежде чем он полностью оптимизирован.
Сделайте следующее (основываясь на вашем примере):
enum Human {
MAN, WOMAN, DEAD; // etc.
}
public void doStuff(Human human) {
switch (human) {
case MAN:
case DEAD:
// do something
break;
// more common cases
}
}
Если ты хочешь EnumSet
тогда вы не можете использовать switch
и должен рефакторинг его if
public void doStuff(EnumSet<Human> human) {
if( human.containsAll(EnumSet.<Human>of(Human.MAN, Human.DEAD) {
// do something
}
}
последний вариант будет делать побитовое сравнение внутри.