Pet*_*ček 7 java enums switch-statement enumset bit-fields
旧的方式,如果我们想要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
}
}
Run Code Online (Sandbox Code Playgroud)
如今,因为我们使用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
}
}
Run Code Online (Sandbox Code Playgroud)
这是不行的,因为我们只能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
}
}
Run Code Online (Sandbox Code Playgroud)
仍然没有运气.知道切换字符串的技巧和EnumSets实际上是64位字段(或它们的数组),我也会尝试:
switch (human.hashCode()) {
case (Human.MAN.hashCode() | Human.DEAD.hashCode()):
// do something
break;
// more common cases
}
Run Code Online (Sandbox Code Playgroud)
认为当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
}
}
Run Code Online (Sandbox Code Playgroud)
特别是,由于引进switch
上Strings
,我相信有至少两种可能的实现switch
上EnumSets
:
case (Human.MAN | Human.DEAD)
表达式中,简单地使用编译时类型检查而ordinal()
不是枚举本身.hashCode()
的name
枚举值的(也可能是一些额外的-枚举值的数量,ordinal()
等等-一切都是静态的,从编译时间常数).是的,这将意味着改变hashCode()
了任EnumSet
类或Enum
类.现在,是否有任何严重的障碍我没有考虑(我可以拿出一些,所有都可以很容易克服),这将使这不可能轻易实现?或者我是对的,这确实是可能的,但不足以让Oracle实现它,因为它不经常使用?
另外,让我说这是一个纯粹的学术问题,可能没有一个好的答案(不知道,我不会另外要求).如果它被证明是无法回答的话,我可能会把它变成社区维基.但是,我无法在任何地方找到答案(甚至任何人都在讨论它),所以在这里.
在Java和面向对象的世界中,你将拥有一个带有setter和getter的类,你可以使用它们
public void doStuff(Human human) {
if(human.isDead()) {
if(human.isMale()) {
// something
} else if (human.isFemale()) {
// something else
} else {
// neither
}
}
}
Run Code Online (Sandbox Code Playgroud)
注意:切换不是一个好主意,因为它只需要完全匹配.例如,除非你只想匹配那些在他们去世前没有饥饿的人,否则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;
}
}
Run Code Online (Sandbox Code Playgroud)
版画
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
Run Code Online (Sandbox Code Playgroud)
在完全优化之前,我的机器上有0.1个时钟周期.
如何使用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
}
}
Run Code Online (Sandbox Code Playgroud)
实际上 EnumSet 的Set
接口方法实现非常有效,下面是您正在寻找的位域比较。