启用EnumSet

时间:2012-12-28 18:11:22

标签: java enums switch-statement bit-fields 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
    }
}

如今,由于我们使用enumsEnumSets,我有时也喜欢做类似的事情:

enum Human {
    MAN, WOMAN, DEAD; // etc.
}

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

不起作用,因为我们switchintenum值只能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
    }
}

仍然没有运气。 Knowing the trick for switch on 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表达式必须是常量表达式


现在,我想知道为什么没有可能这样做。我一直认为Java中的enumsEnumSets就像是对那些老式位域的正确替代,但似乎新方法无法处理更复杂的情况。

与任何switch可能性相比,正确的解决方案很糟糕:

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

特别是,自switch引入Strings以来,我认为switchEnumSets至少有两种可能的实施方式:

  1. case (Human.MAN | Human.DEAD)表达式中,简单地使用编译时类型检查和ordinal()而不是枚举本身。
  2. 使用the same trick as for Strings
    • 在编译时,计算枚举值的hashCode()name (可能还有其他内容 - 枚举中的值的数量,{{1}等等 - 从编译时开始,一切都是静态的和不变的。是的,这意味着要更改ordinal()班级或hashCode()班级的EnumSet
    • 使用而不是枚举本身
  3. 现在,是否有任何严重的障碍我没有考虑(我可以拿出一些,所有都可以轻易克服),这将使这个不可能轻易实现?或者我是对的,这确实是可能的,但不足以让Oracle实现它,因为它不经常使用?


    另外,让我说这是一个纯粹的学术问题可能没有一个好的答案(不知道,我不会另外问)。如果它被证明是无法回答的话,我可能会把它变成社区维基。但是,我无法在任何地方找到答案(甚至任何人都在讨论它),所以就这样了。

3 个答案:

答案 0 :(得分:6)

在Java&amp;面向对象的世界你将拥有一个带有setter和getter的类,你可以使用那些

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个时钟周期。

答案 1 :(得分:4)

如何使用Set的{​​{1}}方法。

EnumSet

Acutally 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 } } 接口方法的实现非常有效,下面是您正在寻找的位域比较。

答案 2 :(得分:-4)

执行以下操作(基于您的示例):

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
    }
}

后一种变体将在内部进行逐位比较。