2021-05-04 19:36  阅读(2248)
文章分类:从 JDK 源码看 Java 文章标签:JavaJDK 源码
©  原文作者:超人汪小建(seaboat) 原文地址:https://blog.csdn.net/wangyangzhizhou/column/info/16032

前言

对于比较稳定的值集合,Java 提供了枚举来定义,通过它可以很方便管理集合。那么 Java 的枚举是通过怎样的机制实现的?本文将从 JDK 角度来看看枚举的原理。

定义枚举

使用很简单,比如定义一个表示“环保”、“交通”、“手机”三个值的集合,那么就可以直接定义如下,然后可直接 Labels.ENVIRONMENT 使用,

    
        public enum Labels {
    
          ENVIRONMENT(), TRAFFIC(), PHONE();
    
        }
    

同时也可以使用带构造函数的枚举,如下,可以通过 getName 获取值。

        public enum Labels0 {
    
          ENVIRONMENT("环保"), TRAFFIC("交通"), PHONE("手机");
    
          private String name;
    
          private Labels0(String name) {
            this.name = name;
          }
    
          public String getName() {
            return name;
          }
    
        }
    
    

编译器做了什么

Java中的枚举的实现机制是怎样的?枚举看起来有点像上帝扔给我们的语法糖,秉着深入挖一挖的精神,看看枚举是相关实现,看看编译器做了什么。用 javap 看上面两个枚举编译后的字节码:

        public final class com.seaboat.Labels extends java.lang.Enum<com.seaboat.Labels> {
          public static final com.seaboat.Labels ENVIRONMENT;
          public static final com.seaboat.Labels TRAFFIC;
          public static final com.seaboat.Labels PHONE;
          static {};
          public static com.seaboat.Labels[] values();
          public static com.seaboat.Labels valueOf(java.lang.String);
        }
    
        public final class com.seaboat.Labels0 extends java.lang.Enum<com.seaboat.Labels0> {
          public static final com.seaboat.Labels0 ENVIRONMENT;
          public static final com.seaboat.Labels0 TRAFFIC;
          public static final com.seaboat.Labels0 PHONE;
          static {};
          public java.lang.String getName();
          public static com.seaboat.Labels0[] values();
          public static com.seaboat.Labels0 valueOf(java.lang.String);
        }
    

可以清晰地看到枚举被编译后其实就是一个类,该类被声明成 final,说明其不能被继承,同时它继承了 Enum 类。枚举里面的元素被声明成 static final ,另外生成一个静态代码块 static{},最后还会生成 values 和 valueOf 两个方法。下面以最简单的 Labels 为例,一个一个模块来看。

Enum 类

Enum 类是一个抽象类,主要有 name 和 ordinal 两个属性,分别用于表示枚举元素的名称和枚举元素的位置索引,而构造函数传入的两个变量刚好与之对应。
* toString 方法直接返回 name。
* equals 方法直接用 == 比较两个对象。
* hashCode 方法调用的是父类的 hashCode 方法。
* 枚举不支持 clone、finalize 和 readObject 方法。
* compareTo 方法可以看到就是比较 ordinal 的大小。
* valueOf 方法,根据传入的字符串 name 来返回对应的枚举元素。

        public abstract class Enum<E extends Enum<E>>
                implements Comparable<E>, Serializable {
    
            private final String name;
    
            private final int ordinal;
    
            public final String name() {
                return name;
            }
    
            public final int ordinal() {
                return ordinal;
            }
    
            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();
            }
    
            protected final Object clone() throws CloneNotSupportedException {
                throw new CloneNotSupportedException();
            }
    
            public final int compareTo(E o) {
                Enum<?> other = (Enum<?>)o;
                Enum<E> self = this;
                if (self.getClass() != other.getClass() &&
                    self.getDeclaringClass() != other.getDeclaringClass())
                    throw new ClassCastException();
                return self.ordinal - other.ordinal;
            }
    
            @SuppressWarnings("unchecked")
            public final Class<E> getDeclaringClass() {
                Class<?> clazz = getClass();
                Class<?> zuper = clazz.getSuperclass();
                return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
            }
    
            public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                                        String name) {
                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);
            }
    
            @SuppressWarnings("deprecation")
            protected final void finalize() { }
    
            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");
            }
        }
    

静态代码块

可以看到静态代码块主要完成的工作就是先分别创建 Labels 对象,然后将“ENVIRONMENT”、“TRAFFIC”和“PHONE”字符串作为 name ,按照顺序分别分配位置索引0、1、2作为 ordinal,然后将其值设置给创建的三个 Labels 对象的 name 和 ordinal 属性,此外还会创建一个大小为3的 Labels 数组 ENUM$VALUES,将前面创建出来的 Labels 对象分别赋值给数组。

        static {};
            descriptor: ()V
            flags: ACC_STATIC
            Code:
              stack=4, locals=0, args_size=0
                 0: new           #1                  // class com/seaboat/Labels
                 3: dup
                 4: ldc           #14                 // String ENVIRONMENT
                 6: iconst_0
                 7: invokespecial #15                 // Method "<init>":(Ljava/lang/String;I)V
                10: putstatic     #19                 // Field ENVIRONMENT:Lcom/seaboat/Labels;
                13: new           #1                  // class com/seaboat/Labels
                16: dup
                17: ldc           #21                 // String TRAFFIC
                19: iconst_1
                20: invokespecial #15                 // Method "<init>":(Ljava/lang/String;I)V
                23: putstatic     #22                 // Field TRAFFIC:Lcom/seaboat/Labels;
                26: new           #1                  // class com/seaboat/Labels
                29: dup
                30: ldc           #24                 // String PHONE
                32: iconst_2
                33: invokespecial #15                 // Method "<init>":(Ljava/lang/String;I)V
                36: putstatic     #25                 // Field PHONE:Lcom/seaboat/Labels;
                39: iconst_3
                40: anewarray     #1                  // class com/seaboat/Labels
                43: dup
                44: iconst_0
                45: getstatic     #19                 // Field ENVIRONMENT:Lcom/seaboat/Labels;
                48: aastore
                49: dup
                50: iconst_1
                51: getstatic     #22                 // Field TRAFFIC:Lcom/seaboat/Labels;
                54: aastore
                55: dup
                56: iconst_2
                57: getstatic     #25                 // Field PHONE:Lcom/seaboat/Labels;
                60: aastore
                61: putstatic     #27                 // Field ENUM$VALUES:[Lcom/seaboat/Labels;
                64: return
              LineNumberTable:
                line 5: 0
                line 3: 39
              LocalVariableTable:
                Start  Length  Slot  Name   Signature
    

values 方法

可以看到它是一个静态方法,主要是使用了前面静态代码块中的 Labels 数组 ENUM$VALUES,调用 System.arraycopy 对其进行复制,然后返回该数组。所以通过 Labels.values()[2]就能获取到数组中索引为2的元素。

          public static com.seaboat.Labels[] values();
            descriptor: ()[Lcom/seaboat/Labels;
            flags: ACC_PUBLIC, ACC_STATIC
            Code:
              stack=5, locals=3, args_size=0
                 0: getstatic     #27                 // Field ENUM$VALUES:[Lcom/seaboat/Labels;
                 3: dup
                 4: astore_0
                 5: iconst_0
                 6: aload_0
                 7: arraylength
                 8: dup
                 9: istore_1
                10: anewarray     #1                  // class com/seaboat/Labels
                13: dup
                14: astore_2
                15: iconst_0
                16: iload_1
                17: invokestatic  #35                 // Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V
                20: aload_2
                21: areturn
              LineNumberTable:
                line 1: 0
              LocalVariableTable:
                Start  Length  Slot  Name   Signature
    

valueOf 方法

该方法同样是个静态方法,可以看到该方法的实现是间接调用了父类 Enum 类的 valueOf 方法,根据传入的字符串 name 来返回对应的枚举元素,比如可以通过 Labels.valueOf("ENVIRONMENT")获取 Labels.ENVIRONMENT

        public static com.seaboat.Labels valueOf(java.lang.String);
            descriptor: (Ljava/lang/String;)Lcom/seaboat/Labels;
            flags: ACC_PUBLIC, ACC_STATIC
            Code:
              stack=2, locals=1, args_size=1
                 0: ldc           #1                  // class com/seaboat/Labels
                 2: aload_0
                 3: invokestatic  #43                 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
                 6: checkcast     #1                  // class com/seaboat/Labels
                 9: areturn
              LineNumberTable:
                line 1: 0
              LocalVariableTable:
                Start  Length  Slot  Name   Signature
    

总结

枚举本质其实也是一个类,而且都会继承java.lang.Enum类,同时还会生成一个静态代码块 static{},并且还会生成 values 和 valueOf 两个方法。而上述的工作都需要由编译器来完成,然后我们就可以像使用我们熟悉的类那样去使用枚举了。

点赞(1)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> 从JDK角度认识枚举enum
上一篇
细看Java序列化机制
下一篇
从JDK角度看对象克隆