先上一段能编译通过的程序:
enum Direction {
EAST,
WEST,
SOUTH,
NORTH;
}
public class SimpleEnum {
public static void main(String args[]){
Direction d = Direction.EAST;
switch(d) {
case EAST:
System.out.println("Dragon");
break;
case WEST:
System.out.println("Tiger");
break;
case SOUTH:
System.out.println("Peacock");
break;
case NORTH:
System.out.println("Tortoise");
}
}
}
我的疑问是: 为什么程序中对 enum 常量的引用不一致?
在 main
函数中,
Direction d = Direction.EAST;
如果写成
Direction d = EAST;
javac 会报错,说找不到符号 EAST
.
而switch case的代码块中必须直接跟枚举常量,
如果把 case EAST:
改成 case Direction.EAST:
,
javac 会报告说: 枚举 switch case 标签必须为枚举常量的非限定名称。
这种形式的不一致让人有些不爽。为什么会有这种不一致呢?
enum是jdk5引入的语法糖,定义一个enum类实际上也是定义一个class,只是通过enum定义这个特殊class的时候,编译器会帮你做些事情:
所以,枚举类和普通类的用法没有太大的区别,譬如:
根据上面的描述:
Direction d = Direction.EAST;
的答案就很显然了,赋值枚举变量的时候,当然要带前缀了,因为这些枚举常量是指定枚举类中的常量,必须加上类限定前缀。java的switch语法,是通过jvm的tableswitch和lookupswitch两个指令实现。简单说一下实现原理:java编译器为switch语句编译成一个局部变量数组,每个case对应一个数组的索引,指令的执行是通过不同的数组索引找到不同的入口指令。所以原则上switch...case只能处理int型的变量。
enum能用在switch语句中,也是一个语法糖,我们知道所有枚举类的父类Enum中有一个
private final int ordinal;
,java编译器检测到switch语句中变量是一个枚举类,则会利用之前枚举类的ordinal
属性,编译一个局部变量数组,后续在进行case分支比较的时候,就是简单通过tableswitch或lookupswitch指令来进行跳转,需要注意的一点:这个局部变量数组的构建过程是在编译器在编译阶段完成的。给一个简单的实例:
根据上述的描述:
ordinal
属性为索引,通过tableswitch查找局部变量数组(这在反编译后的7~12行字节码可以体现出)。在此时case语句不需要类限定前缀,完全是java编译器的限制(编译器是不需要枚举类的前缀,只需要枚举类编译的static int[] $SWITCH_TABLE
)。PS: JDK7中switch...case语法新增支持string字面量是差不多同样的道理,java编译器根据string常量的hashcode值,在编译阶段构建局部常量数组。