先上一段能编译通过的程序:
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的时候,编译器会帮你做些事情:
1.所有的枚举类会默认继承Enum类
2.所有的枚举类是final的,不可扩展子类
3.所有定义的枚举常量,会生成定义的枚举类中生成public static final常量
所以,枚举类和普通类的用法没有太大的区别,譬如:
case1:
public enum TrafficLight {
RED("红灯"),
GREEN("绿灯"),
YELLOW("黄灯"),
;
private final String desc;
TrafficLight (String desc) { this.desc = desc; }
public String getDesc() { return desc; }
}
case2:
public enum TrafficLight {
RED("红灯") {
public TrafficLight next() { return YELLOW; }
},
GREEN("绿灯") {
public TrafficLight next() { return RED; }
},
YELLOW("黄灯") {
public TrafficLight next() { return GREEN; }
},
;
private final String desc;
TrafficLight (String desc) { this.desc = desc; }
public String getDesc() { return desc; }
public abstract TrafficLight next();
}
根据上面的描述:
第一个问题: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指令来进行跳转,需要注意的一点:这个局部变量数组的构建过程是在编译器在编译阶段完成的。
给一个简单的实例:
public class Traffic {
public static void main(String[] args) {
Light light = Light.RED;
switch(light) {
case RED:
System.out.println("红灯");
break;
case GREEN:
System.out.println("绿灯");
break;
case YELLOW:
System.out.println("黄灯");
break;
}
}
enum Light { RED, GREEN, YELLOW }
}
javap -c 反编译后的字节码:
public class com.lee.test.Traffic extends java.lang.Object{
public com.lee.test.Traffic();
Code:
0: aload_0
1: invokespecial #10; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #18; //Field com/lee/test/Traffic$Light.RED:Lcom/lee/test/Traffic$Light;
3: astore_1
4: invokestatic #24; //Method $SWITCH_TABLE$com$lee$test$Traffic$Light:()[I
7: aload_1
8: invokevirtual #27; //Method com/lee/test/Traffic$Light.ordinal:()I
11: iaload
12: tableswitch{ //1 to 3
1: 40;
2: 51;
3: 62;
default: 70 }
40: getstatic #31; //Field java/lang/System.out:Ljava/io/PrintStream;
43: ldc #37; //String 红灯
45: invokevirtual #39; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
48: goto 70
51: getstatic #31; //Field java/lang/System.out:Ljava/io/PrintStream;
54: ldc #45; //String 绿灯
56: invokevirtual #39; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
59: goto 70
62: getstatic #31; //Field java/lang/System.out:Ljava/io/PrintStream;
65: ldc #47; //String 黄灯
67: invokevirtual #39; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
70: return
static int[] $SWITCH_TABLE$com$lee$test$Traffic$Light();
Code:
0: getstatic #53; //Field $SWITCH_TABLE$com$lee$test$Traffic$Light:[I
3: dup
4: ifnull 8
7: areturn
8: pop
9: invokestatic #55; //Method com/lee/test/Traffic$Light.values:()[Lcom/lee/test/Traffic$Light;
12: arraylength
13: newarray int
15: astore_0
16: aload_0
17: getstatic #59; //Field com/lee/test/Traffic$Light.GREEN:Lcom/lee/test/Traffic$Light;
20: invokevirtual #27; //Method com/lee/test/Traffic$Light.ordinal:()I
23: iconst_2
24: iastore
25: goto 29
28: pop
29: aload_0
30: getstatic #18; //Field com/lee/test/Traffic$Light.RED:Lcom/lee/test/Traffic$Light;
33: invokevirtual #27; //Method com/lee/test/Traffic$Light.ordinal:()I
36: iconst_1
37: iastore
38: goto 42
41: pop
42: aload_0
43: getstatic #62; //Field com/lee/test/Traffic$Light.YELLOW:Lcom/lee/test/Traffic$Light;
46: invokevirtual #27; //Method com/lee/test/Traffic$Light.ordinal:()I
49: iconst_3
50: iastore
51: goto 55
54: pop
55: aload_0
56: dup
57: putstatic #53; //Field $SWITCH_TABLE$com$lee$test$Traffic$Light:[I
60: areturn
Exception table:
from to target type
16 25 28 Class java/lang/NoSuchFieldError
29 38 41 Class java/lang/NoSuchFieldError
42 51 54 Class java/lang/NoSuchFieldError
}
}
根据上述的描述:
枚举变量的定义、赋值是运行时jvm强制要求类型必须一致,所以必须加上类限定前缀;而在switch...case中使用枚举变量,则只是java提供的语法糖,这个特色并不是在运行时通过JVM来保证的,而只是java编译器在编译阶段完成的,在编译过程中的一旦判断switch语句中的变量是enum类型,即只需要以其ordinal属性为索引,通过tableswitch查找局部变量数组(这在反编译后的7~12行字节码可以体现出)。在此时case语句不需要类限定前缀,完全是java编译器的限制(编译器是不需要枚举类的前缀,只需要枚举类编译的static int[] $SWITCH_TABLE)。
JDK7中switch...case语法新增支持string字面量是差不多同样的道理,java编译器根据string常量的hashcode值,在编译阶段构建局部常量数组。
版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。