8

旧的方式,如果我们想要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
    }
}

如今,由于我们使用enumsand 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,enumString值。在这一点上,我意识到这是不可能的,即使这些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
    }
}

仍然没有运气。知道打开字符串的技巧并且 EnumSets 实际上是 64 位字段(或它们的数组),我也会尝试:

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

认为当Human hashCode()正确实施以产生一致的结果时,它可能会起作用。没有:

java.lang.Error:未解决的编译问题:case 表达式必须是常量表达式


现在,我想知道为什么没有可能做到这一点。我一直认为,enumsEnumSetsJava 中,它是那些老式位域的适当替代品,但在这里,新方法似乎无法处理更复杂的情况。

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

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

特别是,自从引入了switchon ,我相信onStrings至少有两种可能的实现:switchEnumSets

  1. case (Human.MAN | Human.DEAD)表达式中,简单地使用编译时类型检查而ordinal()不是枚举本身。
  2. 使用与 Strings 相同的技巧
    • 在编译时,计算枚举值的 (可能还有一些额外的东西 - 枚举中的值的数量hashCode() nameordinal()- 从编译时开始,一切都是静态和恒定的)。是的,这将意味着更改班级或hashCode()班级。EnumSetEnum
    • 使用而不是枚举本身

现在,是否有任何我没有考虑到的严重障碍(我可以想出一些,都可以很容易地克服)会使这不可能轻松实现?或者我认为这确实是可能的,但对于 Oracle 来说还不足以实现它,因为它不经常使用,这是对的吗?


另外,让我声明这是一个纯粹的学术问题,可能没有好的答案(不知道,否则我不会问)。如果它被证明是无法回答的,我可能会将其设为社区 wiki。但是,我在任何地方都找不到答案(甚至任何人都在讨论它),所以就这样吧。

4

3 回答 3

6

在 Java 和面向对象的世界中,您将在 Object 上使用带有 setter 和 getter 的类,您将使用这些

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

注意: switch 不是一个好主意,因为它只需要完全匹配。egcase 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 个时钟周期,在它被完全优化之前。

于 2012-12-28T18:16:29.680 回答
5

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

Acutally EnumSet 的Set接口方法实现非常高效,下面是您正在寻找的位域比较。

于 2012-12-28T18:20:00.167 回答
-5

执行以下操作(根据您的示例):

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

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

如果你想要EnumSet's 那么你不能使用switch并且应该将它重构为if

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

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

于 2012-12-28T18:20:22.853 回答