Включить 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
    }

думая, что когда HumanhashCode() будет правильно реализовано, чтобы дать последовательные результаты, это может работать. Нету:

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:

  1. в case (Human.MAN | Human.DEAD) выражения, просто используйте проверку типа во время компиляции и ordinal() вместо самих перечислений.
  2. Используя тот же трюк, что и для строк.
    • Во время компиляции вычислите 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
    }
}

последний вариант будет делать побитовое сравнение внутри.

Другие вопросы по тегам