java枚举的实现原理

2020-08-21  本文已影响0人  毛发旺盛

基本使用

首先,所有枚举类型都有一个基类:java.lang.Enum抽象类,里面提供了一些基础属性和基础方法。
枚举类型不仅可以定义枚举常量,还可以定义属性、构造方法、普通方法、抽象方法等,比如,我们定义了一个包含成员变量,构造方法以及抽象方法的四则运算操作符枚举类型:

public enum Operator {
    ADD("+") {
        @Override
        public int calculate(int a, int b) {
            return a + b;
        }
    },
    SUB("-") {
        @Override
        public int calculate(int a, int b) {
            return a - b;
        }
    },
    MUL("*") {
        @Override
        public int calculate(int a, int b) {
            return a * b;
        }
    },
    DIV("/") {
        @Override
        public int calculate(int a, int b) {
            return a / b;
        }
    };

    Operator (String operator) {
        this.operator = operator;
    }

    private String operator;

    public abstract int calculate(int a, int b);

    public String getOperator() {
        return operator;
    }
}

Operator表示枚举类型,其中的ADD、SUB等则是具体的枚举值,可理解为Operator类型的四个实例对象。
这样看起来,枚举类型其实也很好理解,就是一个继承了Enum类的普通类(根据后面解析,这个枚举类型实际上仍是个抽象类),它将其所有的实例对象都写在了类内部,类外部只能引用这些实例对象而不能重新创建新的实例对象。(根据后面解析,每个枚举值都对应一个内部类,是这个内部类的实例对象,而这个内部类继承了枚举类)

我们利用反编译信息继续挖掘其内部原理。

原理分析

使用javap Operator.class指令反编译上面的枚举类型Operator,得到的信息如下:

public abstract class Operator extends java.lang.Enum<Operator> {
  public static final Operator ADD;
  public static final Operator SUB;
  public static final Operator MUL;
  public static final Operator DIV;
  public static Operator[] values();
  public static Operator valueOf(java.lang.String);
  public abstract int calculate(int, int);
  public java.lang.String getOperator();
  Operator(java.lang.String, int, java.lang.String, Operator$1);
  static {};
}

从.class文件的反编译信息中可以得出:

  1. 一个枚举类型实际上是一个继承了Enum类的抽象类,这也就解释了为什么在类外部无法创建枚举类型的新的实例对象。
  2. 而具体的枚举值则是Operator抽象类的静态final实例,并且生成Operator.class时也生成了四个Operator$1~4.class文件,所以枚举值实际上是继承了Operator抽象类的内部类Operator1、Operator2、...的对象实例。
  3. 编译出来的Operator类多出了两个静态方法:values和valueOf,还生成了一个静态代码块static{};
  4. 构造方法的参数变成了三个,多出了前面两个参数。这两个参数对应的是其父类Enum的构造参数的两个属性值:name和ordinal。

使用javap -c -v Operator.class查看更详细的反汇编信息,其中重要的信息点:

  1. InnerClasses字段,声明了其中包含了四个内部类Operator$1~4。


    InnerClasses
  2. 静态代码块的具体执行内容如下图分析:


    静态代码块

    根据图中注释,静态代码块中分别创建了四个内部类的实例对象,并分别保存到成员变量ADD,SUB,MUL和DIV中,同时保存到一个新创建的长度为4类型为Operator的数组中,最终用编译器生成的静态字段$VALUES指向该数组。

  3. 编译器生成的values方法
    该方法是一个公共静态方法,可通过调用该方法返回这个枚举类型的枚举值数组,即返回其中声明的所有实例对象。
    该方法实际是调用clone方法来获取$VALUES字段的值,并把类型强转为Operatro[]类型返回。
    实际上并不是真正意义上的clone,并没有创建新的实例对象,仍是原有的在静态代码块中创建的实例对象。
 public static Operator[] values();
    descriptor: ()[LOperator;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: getstatic     #2                  // Field $VALUES:[LOperator;
         3: invokevirtual #3                  // Method "[LOperator;".clone:()Ljava/lang/Object;
         6: checkcast     #4                  // class "[LOperator;"
         9: areturn
  1. 编译器生成的valueOf方法
    该方法也是一个公共静态方法,可通过调用该方法返回参数String对应的枚举值。该方法会调用其父类Enum的valueOf方法,并把类型强转为Operator。
  public static Operator valueOf(java.lang.String);
    descriptor: (Ljava/lang/String;)LOperator;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: ldc           #5                  // class Operator
         2: aload_0
         3: invokestatic  #6                  // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
         6: checkcast     #5                  // class Operator
         9: areturn
  1. 构造方法增加了两个参数
    Enum类有一个带有两个参数的构造方法,name和ordinal是Enum类中为每个枚举定义的两个final属性,name表示我们定义的枚举常量的名称,ordinal则是枚举常量对应的一个序号,该序号从0开始,按照声明顺序赋给每个枚举常量。
    protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }

Enum类中还定义了clone、readObject等方法,其中clone方法为final类型,这两个方法主要用于保证枚举类型的不可变性,不能通过克隆,序列化攻击来复制枚举,保证一个枚举常量只有一个实例。

小结

枚举类型本质上就是一个普通类,经编译器处理后,枚举类型实际上是一个继承了Enum类的抽象类,且编译器为其添加了values和valueOf两个方法。
每一个枚举常量实际上是一个继承了枚举抽象类的内部类的一个实例对象,所有枚举常量都是在静态代码块中初始化,即在类加载期间就初始化。

参考:https://blog.csdn.net/mhmyqn/article/details/48087247

上一篇下一篇

猜你喜欢

热点阅读