从JDK角度认识枚举enum

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】
免费领取10G资料包与项目实战视频资料

作者:超人汪小建(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) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » 从JDK角度认识枚举enum

  • 暂无文章

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏