深入理解枚举类型

枚举类型是一种语法糖:它只是一种语法,是为了方便程序的编写,在编译器编译之后,它将被解读成标准的class类。也就是说,枚举仅仅是披在java语法上的美丽外衣。
接下来我将根据一个简单的枚举类,通过分析它的反编译的class文件来理解枚举类的实质。

1. 枚举可以这样理解:

如果你想创建这样一个类:我只想new固定数量的实例对象,且不能多也不能少。

那么你可能就需要使用枚举类型。

枚举是满足这样要求的特殊类:它有固定大小的实例对象,且不能在外部new新对象,因为他的构造器是private私有的。

枚举将它自身的实例化对象,包含在了自身的字段里,包裹起来,并被构造成公有的(public),静态的(static),且不能改变的(fianl)对象。

2. 如下面这个枚举类:
public enum TestEnum{
	ONE("one"),TWO("two"),TREE("tree");
	private String name;
	private TestEnum(String name){
		this.name=name;
	}
	public void show(){
		System.out.println(name);
	};
	public static void main(String[] args){
		TestEnum e0=TestEnum.ONE;
		TestEnum e1=TestEnum.TWO;
		TestEnum e2=TestEnum.TREE;
		e0.show();
		e1.show();
		e2.show();
	}
}

我们可以使用javap反编译出它的字节码来分析,我将它放在了文章末尾。

3. 分析它的反编译文件:
  1. 首先从下面这行可以看出,枚举最终会被编译成一个普通的class类,他继承自Enum这个抽象类
    并且它是:ACC_FINAL,代表不能被继承。在这里插入图片描述
    Enum的定义如下:
    public abstract class Enum<E extends Enum<E>> extends Object implements Comparable<E>, Serializable {//...}

  2. 从下图可以看出,枚举是将他的常量编译成自身的对象,并且用public ,static ,final修饰,包裹在自身,作为一个字段。在这里插入图片描述

  3. 如果你去关注过Enum类的源码,你会发现,其中并没有我们使用枚举时经常用到的values(),valueOf() 方法。那我们到底调用的什么呢?Enum里没写,我们自己肯定也没写,那函数去哪儿了呢?
    答案是:编译器会自动生成这两个函数,如下图在这里插入图片描述
    在values()函数的Code属性中可以看到
    0: getstatic #1 //Field $VALUES:[LTestEnum
    它的作用就是获得 $VALUES 这个引用指向的数组,并压入栈顶,这个引用也是编译器自动添加的,可以从后面的static{}中看到它如何初始化的,这个后面会讲到
    3: invokevirtual #2 // Method "[LTestEnum;".clone:() Ljava/lang/Object;
    调用$VALUES数组的clone()方法,这是Object继承的方法。
    6:checkcast #3 //class "[LTestEnum;"
    检查上面复制的数组是否是TestEnum[] 类型,如果不是抛出ClassCastException。
    对于valueOf()方法,大致也是调用Enum的valueOf()方法来获得制定名称的TestEnum对象。

  4. 最后的关键是static{}这个静态代码块,它也是编译器自动生成,对于TestEnum类来说,它的作用有三个:

    1. 创建ONE,TWO,TREE三个实例对象,并初始化。
      在这里插入图片描述在这里插入图片描述在这里插入图片描述
      9: invokespecial #16 这句话的作用就是调用Enum类的实例构造器,传入对象的名称ONE,以及它的序数0,以及他的值one。
      12:putstatic #10 作用是将刚创建的对象赋值给字段ONE。
    2. 创建并初始化隐藏的字段$VALUES,上边说过,这是存储枚举常量的数组,它是TestEnum[]类型。在这里插入图片描述
      45:iconst_3 将int类型的3压入栈顶,这个数,作为下一句创建数组的大小。
      46:anewarray #4 //class TestEnum创建一个TestEnum[] 数组引用,并压入栈顶,大小为3
      第49-54,55-60,61-66含义是完全一样的,它们的作用就是获得静态字段,放入到数组指定的位置中。
    3. 将刚才创建的数组赋值给$VALUSE(),并返回在这里插入图片描述
4. 反编译文件中一些访问标识和标志
  1. 访问标志:主要用在类,接口,枚举等类型的 flags属性。在这里插入图片描述
  2. 描述符标识字符含义:主要用来描述方法的返回值类型,参数类型。在这里插入图片描述
  3. 字段的访问标志:主要用在字段的flags属性。在这里插入图片描述

TestEnum类的反编译全文:
C:\Users\69173\Desktop\java语法糖\枚举类>javap -verbose TestEnum.class
Classfile /C:/Users/69173/Desktop/java语法糖/枚举类/TestEnum.class
  Last modified 2019-10-5; size 1272 bytes
  MD5 checksum 049b33fef1ba9ac5d6dd7d196b9c7cb1
  Compiled from "TestEnum.java"
public final class TestEnum extends java.lang.Enum<TestEnum>
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
Constant pool:
   #1 = Fieldref           #4.#48         // TestEnum.$VALUES:[LTestEnum;
   #2 = Methodref          #49.#50        // "[LTestEnum;".clone:()Ljava/lang/Object;
   #3 = Class              #29            // "[LTestEnum;"
   #4 = Class              #51            // TestEnum
   #5 = Methodref          #21.#52        // java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
   #6 = Methodref          #21.#53        // java/lang/Enum."<init>":(Ljava/lang/String;I)V
   #7 = Fieldref           #4.#54         // TestEnum.name:Ljava/lang/String;
   #8 = Fieldref           #55.#56        // java/lang/System.out:Ljava/io/PrintStream;
   #9 = Methodref          #57.#58        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #10 = Fieldref           #4.#59         // TestEnum.ONE:LTestEnum;
  #11 = Fieldref           #4.#60         // TestEnum.TWO:LTestEnum;
  #12 = Fieldref           #4.#61         // TestEnum.TREE:LTestEnum;
  #13 = Methodref          #4.#62         // TestEnum.show:()V
  #14 = String             #22            // ONE
  #15 = String             #63            // one
  #16 = Methodref          #4.#64         // TestEnum."<init>":(Ljava/lang/String;ILjava/lang/String;)V
  #17 = String             #24            // TWO
  #18 = String             #65            // two
  #19 = String             #25            // TREE
  #20 = String             #66            // tree
  #21 = Class              #67            // java/lang/Enum
  #22 = Utf8               ONE
  #23 = Utf8               LTestEnum;
  #24 = Utf8               TWO
  #25 = Utf8               TREE
  #26 = Utf8               name
  #27 = Utf8               Ljava/lang/String;
  #28 = Utf8               $VALUES
  #29 = Utf8               [LTestEnum;
  #30 = Utf8               values
  #31 = Utf8               ()[LTestEnum;
  #32 = Utf8               Code
  #33 = Utf8               LineNumberTable
  #34 = Utf8               valueOf
  #35 = Utf8               (Ljava/lang/String;)LTestEnum;
  #36 = Utf8               <init>
  #37 = Utf8               (Ljava/lang/String;ILjava/lang/String;)V
  #38 = Utf8               Signature
  #39 = Utf8               (Ljava/lang/String;)V
  #40 = Utf8               show
  #41 = Utf8               ()V
  #42 = Utf8               main
  #43 = Utf8               ([Ljava/lang/String;)V
  #44 = Utf8               <clinit>
  #45 = Utf8               Ljava/lang/Enum<LTestEnum;>;
  #46 = Utf8               SourceFile
  #47 = Utf8               TestEnum.java
  #48 = NameAndType        #28:#29        // $VALUES:[LTestEnum;
  #49 = Class              #29            // "[LTestEnum;"
  #50 = NameAndType        #68:#69        // clone:()Ljava/lang/Object;
  #51 = Utf8               TestEnum
  #52 = NameAndType        #34:#70        // valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
  #53 = NameAndType        #36:#71        // "<init>":(Ljava/lang/String;I)V
  #54 = NameAndType        #26:#27        // name:Ljava/lang/String;
  #55 = Class              #72            // java/lang/System
  #56 = NameAndType        #73:#74        // out:Ljava/io/PrintStream;
  #57 = Class              #75            // java/io/PrintStream
  #58 = NameAndType        #76:#39        // println:(Ljava/lang/String;)V
  #59 = NameAndType        #22:#23        // ONE:LTestEnum;
  #60 = NameAndType        #24:#23        // TWO:LTestEnum;
  #61 = NameAndType        #25:#23        // TREE:LTestEnum;
  #62 = NameAndType        #40:#41        // show:()V
  #63 = Utf8               one
  #64 = NameAndType        #36:#37        // "<init>":(Ljava/lang/String;ILjava/lang/String;)V
  #65 = Utf8               two
  #66 = Utf8               tree
  #67 = Utf8               java/lang/Enum
  #68 = Utf8               clone
  #69 = Utf8               ()Ljava/lang/Object;
  #70 = Utf8               (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
  #71 = Utf8               (Ljava/lang/String;I)V
  #72 = Utf8               java/lang/System
  #73 = Utf8               out
  #74 = Utf8               Ljava/io/PrintStream;
  #75 = Utf8               java/io/PrintStream
  #76 = Utf8               println
{
  public static final TestEnum ONE;
    descriptor: LTestEnum;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

  public static final TestEnum TWO;
    descriptor: LTestEnum;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

  public static final TestEnum TREE;
    descriptor: LTestEnum;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

  public static TestEnum[] values();
    descriptor: ()[LTestEnum;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: getstatic     #1                  // Field $VALUES:[LTestEnum;
         3: invokevirtual #2                  // Method "[LTestEnum;".clone:()Ljava/lang/Object;
         6: checkcast     #3                  // class "[LTestEnum;"
         9: areturn
      LineNumberTable:
        line 1: 0

  public static TestEnum valueOf(java.lang.String);
    descriptor: (Ljava/lang/String;)LTestEnum;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: ldc           #4                  // class TestEnum
         2: aload_0
         3: invokestatic  #5                  // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
         6: checkcast     #4                  // class TestEnum
         9: areturn
      LineNumberTable:
        line 1: 0

  public void show();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: aload_0
         4: getfield      #7                  // Field name:Ljava/lang/String;
         7: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        10: return
      LineNumberTable:
        line 8: 0
        line 9: 10

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=4, args_size=1
         0: getstatic     #10                 // Field ONE:LTestEnum;
         3: astore_1
         4: getstatic     #11                 // Field TWO:LTestEnum;
         7: astore_2
         8: getstatic     #12                 // Field TREE:LTestEnum;
        11: astore_3
        12: aload_1
        13: invokevirtual #13                 // Method show:()V
        16: aload_2
        17: invokevirtual #13                 // Method show:()V
        20: aload_3
        21: invokevirtual #13                 // Method show:()V
        24: return
      LineNumberTable:
        line 11: 0
        line 12: 4
        line 13: 8
        line 14: 12
        line 15: 16
        line 16: 20
        line 17: 24

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=5, locals=0, args_size=0
         0: new           #4                  // class TestEnum
         3: dup
         4: ldc           #14                 // String ONE
         6: iconst_0
         7: ldc           #15                 // String one
         9: invokespecial #16                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;)V
        12: putstatic     #10                 // Field ONE:LTestEnum;
        15: new           #4                  // class TestEnum
        18: dup
        19: ldc           #17                 // String TWO
        21: iconst_1
        22: ldc           #18                 // String two
        24: invokespecial #16                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;)V
        27: putstatic     #11                 // Field TWO:LTestEnum;
        30: new           #4                  // class TestEnum
        33: dup
        34: ldc           #19                 // String TREE
        36: iconst_2
        37: ldc           #20                 // String tree
        39: invokespecial #16                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;)V
        42: putstatic     #12                 // Field TREE:LTestEnum;
        45: iconst_3
        46: anewarray     #4                  // class TestEnum
        49: dup
        50: iconst_0
        51: getstatic     #10                 // Field ONE:LTestEnum;
        54: aastore
        55: dup
        56: iconst_1
        57: getstatic     #11                 // Field TWO:LTestEnum;
        60: aastore
        61: dup
        62: iconst_2
        63: getstatic     #12                 // Field TREE:LTestEnum;
        66: aastore
        67: putstatic     #1                  // Field $VALUES:[LTestEnum;
        70: return
      LineNumberTable:
        line 2: 0
        line 1: 45
}
Signature: #45                          // Ljava/lang/Enum<LTestEnum;>;
SourceFile: "TestEnum.java"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值