美文网首页java
java enum实现原理

java enum实现原理

作者: 编码之路从零开始 | 来源:发表于2020-06-15 22:27 被阅读0次

    一、分析自定义枚举类

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

    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<org.example.Operator> {
      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<org.example.Operator> {
      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<E extends Enum<E>> implements Comparable<E>, 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<E> self = this;
            if (self.getClass() != other.getClass() && // optimization
                self.getDeclaringClass() != other.getDeclaringClass())
                throw new ClassCastException();
            return self.ordinal - other.ordinal;
        }
    
        /**
         * 返回该枚举类型正确的Class对象。 
             * 在当前案例中,每一个枚举的Class对象应该是内部类,比如“ADD”对象实际上是Class<Operator$1>,
             * 但这些是虚拟机内部的实现,用户想要看到的是Class<Operator>。
         */
        @SuppressWarnings("unchecked")
        public final Class<E> getDeclaringClass() {
            Class<?> clazz = getClass();
            Class<?> zuper = clazz.getSuperclass();
            return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
        }
    
        /**
         * 返回指定枚举类型中指定名字的枚举常量。该name在每个枚举类型中唯一。
         * 
         * 该方法被每个枚举类型中隐式的{@code public static T valueOf(String)}
         * 所使用。程序应该使用每个枚举类型中的{@code public static T valueOf(String)}。
         */
        public static <T extends Enum<T>> T valueOf(Class<T> 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实现原理

        本文链接:https://www.haomeiwen.com/subject/hvzhxktx.html