开发者社区> 问答> 正文

关于java枚举类型的疑问

先上一段能编译通过的程序:

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 标签必须为枚举常量的非限定名称。
这种形式的不一致让人有些不爽。为什么会有这种不一致呢?

展开
收起
蛮大人123 2016-03-04 18:16:53 4121 0
1 条回答
写回答
取消 提交回答
  • 我说我不帅他们就打我,还说我虚伪

    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值,在编译阶段构建局部常量数组。

    2019-07-17 18:52:43
    赞同 展开评论 打赏
问答分类:
问答地址:
问答排行榜
最热
最新

相关电子书

更多
Spring Cloud Alibaba - 重新定义 Java Cloud-Native 立即下载
The Reactive Cloud Native Arch 立即下载
JAVA开发手册1.5.0 立即下载