java enum实现原理

一、分析自定义枚举类

普通的枚举类和抽象枚举类相似,故直接分析抽象枚举类。

1. 编写一个抽象枚举类

package org.example;

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;
        }
    };

    private String identifier;

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

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

    public String getIdentifier(){
        return identifier;
    }
}

2. 编译

使用命令javac Operator.java文件编译成.class文件。

编译生成如下五个文件:

Operator.class
Operator$1.class
Operator$2.class
Operator$3.class
Operator$4.class

Operator.classOperator的编译结果,但是其余四个.class文件目前不明确来源。

3. 反编译

普通的枚举类的反编译结果使用final进行修饰。

3.1 查看Operator简单内容

通过命令javap Operator反编译Operator.class

反编译结果:

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

反编译之后的结果多了valuesvalueOf以及一个static代码块,而且构造函数的参数和我们写的参数也不一致。但是有用信息太少,需要查看详细的编译信息,用来确定这些新增代码的用途。

3.2 查看Operator详细信息

通过命令javap -c -v Operator查看Operator.class反编译的详细信息。

具体的信息由于篇幅原因不展示具体的编译信息,只展示如下分析结果:

public abstract class org.example.Operator extends java.lang.Enum {
  public static final org.example.Operator ADD;
  public static final org.example.Operator SUB;
  public static final org.example.Operator MUL;
  public static final org.example.Operator DIV;
  private static final org.example.Operator[] $VALUES;
  private String identifier;
  
  // 抽象方法,在子类中实例化
  public abstract int calculate(int, int);
  
  public java.lang.String getIdentifier(){ 
      return identifier;
  }
  
  // 关于编译之后添加的name、ordinal可以在Enum中看出它们的用途
  private org.example.Operator(String name, int ordinal, String identifier){
      super(name, ordinal);
      this.identifier = identifier;
  }
  
  // 看不懂该方法的作用
  org.example.Operator(String name, int ordinal, String identifier, org.example.Operator$1 ){     
      this(name, ordinal, identifier); 
  }
  
  public static org.example.Operator[] values(){
      return (Operator[])$VALUES.clone();
  }
  
  // 根据名字返回对应的枚举常量,具体看Eunm类的分析
  public static org.example.Operator valueOf(String name){
      return (Operator)super.valueOf(Operator.class, name);
  }

  // 实例化枚举类型中定义的常量
  static {
      ADD = new Operator$1("ADD", 0, "+");
      SUB = new Operator$2("SUB", 1, "-");
      MUL = new Operator$3("MUL", 2, "*");
      DIV = new Operator$4("DIV", 3, "/");
      $VALUES = new Operator[4];
      $VALUES[0] = ADD;
      $VALUES[1] = SUB;
      $VALUES[2] = MUL;
      $VALUES[3] = DIV;
  };
}
InnerClasses:
     static #24; //class org/example/Operator$4
     static #19; //class org/example/Operator$3
     static #14; //class org/example/Operator$2
     static #9;  //class org/example/Operator$1

最后的InnerClasses部分解答了之前的疑惑:每一个枚举对象就是一个匿名内部类的实例。该实例在Operator类的static代码块中初始化。并且每个枚举类型内部都会维护一个$VALUES数组,用来保存内部所有的枚举实例。

3.3 查看Operator$1.class详细信息

使用javap -c -v Operator$1查看Operator$1.class反编译的详细信息。
其余的几个和Operator$1.class类似,不做多余解释。

同样只展示分析后的结果:

final class org.example.Operator$1 extends org.example.Operator{
  // 构造函数传入了一个匿名子类的实例(实际上是`null`),搞不懂
  org.example.Operator$1(String name, int ordinal, String identifier){
      super(name, ordinal, identifier, null);
  }

  public int calculate(int a, int b){ 
      return a + b; 
  }
}

很简单的匿名内部类的实现,单纯的实现了父类的抽象方法。

自定义枚举分析完毕,下来看一下Enum类。

二、分析Enum类源码

public abstract class Enum> implements Comparable, Serializable {
    /**
     * 枚举常量的名字,在本案例中是“ADD”、“SUB”、“MUL”、“DIV”。
     * 大多数的程序应该在{@link #toString}中引用该字段,而不是直接访问该字段。
     * 因此{@link #toString}需要返回对用户友好的字符串。
     */
    private final String name;

    public final String name() { return name; }

    /**
     * 枚举常量的定义的顺序(第一个常量的ordinal从0开始)。
     * 大多数的程序中不应该使用该字段。该字段被一些基于枚举的数据结构所使用,例如
     * {@link java.util.EnumSet} and {@link java.util.EnumMap}.
     */
    private final int ordinal;

    public final int ordinal() { return ordinal; }

    /**
     * 唯一的构造方法。程序不能执行该构造方法。它只能被编译器编生成的代码
     * (ACC_SYNTHETIC,比如{@link org.example.Operator(String, int, String)})
     * 所使用。
     *
     * 如果要是通过反射调用该构造方法,也会抛出异常。因为在反射API中对Class的类型做了判断。
     */
    protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }

    /**
     * 该方法应该被重写,并返回一些对用户友好的字符串。
     */
    public String toString() { return name; }

    public final boolean equals(Object other) { return this==other; }

    public final int hashCode() { return super.hashCode(); }

    /** enum classes cannot have finalize methods. */
    protected final void finalize() { }

    /**
     * 该函数是通过ordinal字段来比较的,因此其返回结果和常量定义的顺序有关系。
     * 并且枚举常量只能和相同类型的枚举常量进行比较。
     */
    public final int compareTo(E o) {
        Enum other = (Enum)o;
        Enum self = this;
        if (self.getClass() != other.getClass() && // optimization
            self.getDeclaringClass() != other.getDeclaringClass())
            throw new ClassCastException();
        return self.ordinal - other.ordinal;
    }

    /**
     * 返回该枚举类型正确的Class对象。 
         * 在当前案例中,每一个枚举的Class对象应该是内部类,比如“ADD”对象实际上是Class,
         * 但这些是虚拟机内部的实现,用户想要看到的是Class。
     */
    @SuppressWarnings("unchecked")
    public final Class getDeclaringClass() {
        Class clazz = getClass();
        Class zuper = clazz.getSuperclass();
        return (zuper == Enum.class) ? (Class)clazz : (Class)zuper;
    }

    /**
     * 返回指定枚举类型中指定名字的枚举常量。该name在每个枚举类型中唯一。
     * 
     * 该方法被每个枚举类型中隐式的{@code public static T valueOf(String)}
     * 所使用。程序应该使用每个枚举类型中的{@code public static T valueOf(String)}。
     */
    public static > T valueOf(Class enumType, String name) {
        // 根据name从map中取该常量
        T result = enumType.enumConstantDirectory().get(name);
        if (result != null)
            return result;
        if (name == null)
            throw new NullPointerException("Name is null");
        throw new IllegalArgumentException(
            "No enum constant " + enumType.getCanonicalName() + "." + name);
    }


    /**
     * 该方法抛出一个异常,为了保证枚举常量“单例”的特性。
     */
    protected final Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }

    /**
     * 防止反序列化攻击,为了保证枚举常量“单例”的特性。
     */
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        throw new InvalidObjectException("can't deserialize enum");
    }

    private void readObjectNoData() throws ObjectStreamException {
        throw new InvalidObjectException("can't deserialize enum");
    }
}

三、收获

枚举如何保证单例性

  1. new关键字:
    • 枚举:直接在编译器层面进行了控制。
    • 替代:可以通过构造方法私有化来实现。
  2. 继承:
    • 枚举:在编译器层面做了控制,无法继承一个枚举类。
    • 替代:普通类可以通过final关键字实现。抽象类则无法实现,因为我们没有办法控制一个类只被特定的几个类继承,但是可以通过其他方法间接实现该效果。
  3. 反射API:
    • 枚举:在反射API中进行了控制。
    • 替代:可以在单例对象的构造方法中进行判断来实现。
  4. 反序列化:
    • 枚举:在反序列化用到的方法中进行了控制。
    • 替代:可以通过重写反序列化方法实现。

关于反射API

反射API一般通过Class.newInstance来获取实例对象:

// java.lang.Class
@CallerSensitive
public T newInstance() throws InstantiationException, IllegalAccessException {
    // ... 省略其他代码
    try {
        return tmpConstructor.newInstance((Object[])null);
    } catch (InvocationTargetException e) {
        Unsafe.getUnsafe().throwException(e.getTargetException());
        return null;
    }
}

该方法通过 tmpConstructor.newInstance((Object[])null);新建对象。继续跟踪:

// java.lang.reflect.Constructor
@CallerSensitive
public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
  // ... 省略其他代码
  if ((clazz.getModifiers() & Modifier.ENUM) != 0)
    throw new IllegalArgumentException("Cannot reflectively create enum objects");
  // ... 省略其他代码
}

可以看到反射API对Enum类型做了判断,不会实例化Enum类型。

你可能感兴趣的:(java enum实现原理)