zoukankan      html  css  js  c++  java
  • JDK中枚举的底层实现

    前提

    上一篇文章复习介绍了JDK中注解的底层实现,跟注解一样比较常用,但是底层实现比较神秘的还有枚举类型。趁着国庆假期的最后两天,把JDK中枚举的底层实现也进行一次探究。

    通过例子查找本质

    在探究JDK注解的底层实现的时候,因为预先参考了不少资料,所以整个过程有点"未卜先知"的意味,这里尝试用未知的角度去看注解的底层实现。先定义一个手机操作系统类型枚举PhoneOsEnum:

    package club.throwable.enumeration;
    
    public enum PhoneOsEnum {
    
    	/**
    	 * 安卓
    	 */
    	ANDROID(1, "android"),
    
    	/**
    	 * ios
    	 */
    	IOS(2, "ios");
    
    
    	private final Integer type;
    	private final String typeName;
    
    	PhoneOsEnum(Integer type, String typeName) {
    		this.type = type;
    		this.typeName = typeName;
    	}
    
    	public Integer getType() {
    		return type;
    	}
    
    	public String getTypeName() {
    		return typeName;
    	}
    }
    

    这是一个很简单的枚举,接着使用JDK的反编译工具反编译出其字节码,执行下面的命令:

    javap -c -v D:Projects
    xjava-seed	argetclassesclub	hrowableenumerationPhoneOsEnum.class
    

    然后就得到了关于PhoneOsEnum.class的很长的字节码,这里全部贴出来:

    Classfile /D:/Projects/rxjava-seed/target/classes/club/throwable/enumeration/PhoneOsEnum.class
      Last modified 2018-10-6; size 1561 bytes
      MD5 checksum 6d3186042f54233219000927a2f196aa
      Compiled from "PhoneOsEnum.java"
    public final class club.throwable.enumeration.PhoneOsEnum extends java.lang.Enum<club.throwable.enumeration.PhoneOsEnum>
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
    Constant pool:
       #1 = Fieldref           #4.#49         // club/throwable/enumeration/PhoneOsEnum.$VALUES:[Lclub/throwable/enumeration/PhoneOsEnum;
       #2 = Methodref          #50.#51        // "[Lclub/throwable/enumeration/PhoneOsEnum;".clone:()Ljava/lang/Object;
       #3 = Class              #26            // "[Lclub/throwable/enumeration/PhoneOsEnum;"
       #4 = Class              #52            // club/throwable/enumeration/PhoneOsEnum
       #5 = Methodref          #17.#53        // java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
       #6 = Methodref          #17.#54        // java/lang/Enum."<init>":(Ljava/lang/String;I)V
       #7 = Fieldref           #4.#55         // club/throwable/enumeration/PhoneOsEnum.type:Ljava/lang/Integer;
       #8 = Fieldref           #4.#56         // club/throwable/enumeration/PhoneOsEnum.typeName:Ljava/lang/String;
       #9 = String             #18            // ANDROID
      #10 = Methodref          #57.#58        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      #11 = String             #59            // android
      #12 = Methodref          #4.#60         // club/throwable/enumeration/PhoneOsEnum."<init>":(Ljava/lang/String;ILjava/lang/Integer;Ljava/lang/String;)V
      #13 = Fieldref           #4.#61         // club/throwable/enumeration/PhoneOsEnum.ANDROID:Lclub/throwable/enumeration/PhoneOsEnum;
      #14 = String             #20            // IOS
      #15 = String             #62            // ios
      #16 = Fieldref           #4.#63         // club/throwable/enumeration/PhoneOsEnum.IOS:Lclub/throwable/enumeration/PhoneOsEnum;
      #17 = Class              #64            // java/lang/Enum
      #18 = Utf8               ANDROID
      #19 = Utf8               Lclub/throwable/enumeration/PhoneOsEnum;
      #20 = Utf8               IOS
      #21 = Utf8               type
      #22 = Utf8               Ljava/lang/Integer;
      #23 = Utf8               typeName
      #24 = Utf8               Ljava/lang/String;
      #25 = Utf8               $VALUES
      #26 = Utf8               [Lclub/throwable/enumeration/PhoneOsEnum;
      #27 = Utf8               values
      #28 = Utf8               ()[Lclub/throwable/enumeration/PhoneOsEnum;
      #29 = Utf8               Code
      #30 = Utf8               LineNumberTable
      #31 = Utf8               valueOf
      #32 = Utf8               (Ljava/lang/String;)Lclub/throwable/enumeration/PhoneOsEnum;
      #33 = Utf8               LocalVariableTable
      #34 = Utf8               name
      #35 = Utf8               <init>
      #36 = Utf8               (Ljava/lang/String;ILjava/lang/Integer;Ljava/lang/String;)V
      #37 = Utf8               this
      #38 = Utf8               Signature
      #39 = Utf8               (Ljava/lang/Integer;Ljava/lang/String;)V
      #40 = Utf8               getType
      #41 = Utf8               ()Ljava/lang/Integer;
      #42 = Utf8               getTypeName
      #43 = Utf8               ()Ljava/lang/String;
      #44 = Utf8               <clinit>
      #45 = Utf8               ()V
      #46 = Utf8               Ljava/lang/Enum<Lclub/throwable/enumeration/PhoneOsEnum;>;
      #47 = Utf8               SourceFile
      #48 = Utf8               PhoneOsEnum.java
      #49 = NameAndType        #25:#26        // $VALUES:[Lclub/throwable/enumeration/PhoneOsEnum;
      #50 = Class              #26            // "[Lclub/throwable/enumeration/PhoneOsEnum;"
      #51 = NameAndType        #65:#66        // clone:()Ljava/lang/Object;
      #52 = Utf8               club/throwable/enumeration/PhoneOsEnum
      #53 = NameAndType        #31:#67        // valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
      #54 = NameAndType        #35:#68        // "<init>":(Ljava/lang/String;I)V
      #55 = NameAndType        #21:#22        // type:Ljava/lang/Integer;
      #56 = NameAndType        #23:#24        // typeName:Ljava/lang/String;
      #57 = Class              #69            // java/lang/Integer
      #58 = NameAndType        #31:#70        // valueOf:(I)Ljava/lang/Integer;
      #59 = Utf8               android
      #60 = NameAndType        #35:#36        // "<init>":(Ljava/lang/String;ILjava/lang/Integer;Ljava/lang/String;)V
      #61 = NameAndType        #18:#19        // ANDROID:Lclub/throwable/enumeration/PhoneOsEnum;
      #62 = Utf8               ios
      #63 = NameAndType        #20:#19        // IOS:Lclub/throwable/enumeration/PhoneOsEnum;
      #64 = Utf8               java/lang/Enum
      #65 = Utf8               clone
      #66 = Utf8               ()Ljava/lang/Object;
      #67 = Utf8               (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
      #68 = Utf8               (Ljava/lang/String;I)V
      #69 = Utf8               java/lang/Integer
      #70 = Utf8               (I)Ljava/lang/Integer;
    {
      public static final club.throwable.enumeration.PhoneOsEnum ANDROID;
        descriptor: Lclub/throwable/enumeration/PhoneOsEnum;
        flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
    
      public static final club.throwable.enumeration.PhoneOsEnum IOS;
        descriptor: Lclub/throwable/enumeration/PhoneOsEnum;
        flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
    
      public static club.throwable.enumeration.PhoneOsEnum[] values();
        descriptor: ()[Lclub/throwable/enumeration/PhoneOsEnum;
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=1, locals=0, args_size=0
             0: getstatic     #1                  // Field $VALUES:[Lclub/throwable/enumeration/PhoneOsEnum;
             3: invokevirtual #2                  // Method "[Lclub/throwable/enumeration/PhoneOsEnum;".clone:()Ljava/lang/Object;
             6: checkcast     #3                  // class "[Lclub/throwable/enumeration/PhoneOsEnum;"
             9: areturn
          LineNumberTable:
            line 9: 0
    
      public static club.throwable.enumeration.PhoneOsEnum valueOf(java.lang.String);
        descriptor: (Ljava/lang/String;)Lclub/throwable/enumeration/PhoneOsEnum;
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=1, args_size=1
             0: ldc           #4                  // class club/throwable/enumeration/PhoneOsEnum
             2: aload_0
             3: invokestatic  #5                  // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
             6: checkcast     #4                  // class club/throwable/enumeration/PhoneOsEnum
             9: areturn
          LineNumberTable:
            line 9: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      10     0  name   Ljava/lang/String;
    
      public java.lang.Integer getType();
        descriptor: ()Ljava/lang/Integer;
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: getfield      #7                  // Field type:Ljava/lang/Integer;
             4: areturn
          LineNumberTable:
            line 31: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Lclub/throwable/enumeration/PhoneOsEnum;
    
      public java.lang.String getTypeName();
        descriptor: ()Ljava/lang/String;
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: getfield      #8                  // Field typeName:Ljava/lang/String;
             4: areturn
          LineNumberTable:
            line 35: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Lclub/throwable/enumeration/PhoneOsEnum;
    
      static {};
        descriptor: ()V
        flags: ACC_STATIC
        Code:
          stack=6, locals=0, args_size=0
             0: new           #4                  // class club/throwable/enumeration/PhoneOsEnum
             3: dup
             4: ldc           #9                  // String ANDROID
             6: iconst_0
             7: iconst_1
             8: invokestatic  #10                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
            11: ldc           #11                 // String android
            13: invokespecial #12                 // Method "<init>":(Ljava/lang/String;ILjava/lang/Integer;Ljava/lang/String;)V
            16: putstatic     #13                 // Field ANDROID:Lclub/throwable/enumeration/PhoneOsEnum;
            19: new           #4                  // class club/throwable/enumeration/PhoneOsEnum
            22: dup
            23: ldc           #14                 // String IOS
            25: iconst_1
            26: iconst_2
            27: invokestatic  #10                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
            30: ldc           #15                 // String ios
            32: invokespecial #12                 // Method "<init>":(Ljava/lang/String;ILjava/lang/Integer;Ljava/lang/String;)V
            35: putstatic     #16                 // Field IOS:Lclub/throwable/enumeration/PhoneOsEnum;
            38: iconst_2
            39: anewarray     #4                  // class club/throwable/enumeration/PhoneOsEnum
            42: dup
            43: iconst_0
            44: getstatic     #13                 // Field ANDROID:Lclub/throwable/enumeration/PhoneOsEnum;
            47: aastore
            48: dup
            49: iconst_1
            50: getstatic     #16                 // Field IOS:Lclub/throwable/enumeration/PhoneOsEnum;
            53: aastore
            54: putstatic     #1                  // Field $VALUES:[Lclub/throwable/enumeration/PhoneOsEnum;
            57: return
          LineNumberTable:
            line 14: 0
            line 19: 19
            line 9: 38
    }
    Signature: #46                          // Ljava/lang/Enum<Lclub/throwable/enumeration/PhoneOsEnum;>;
    SourceFile: "PhoneOsEnum.java"
    

    先看类的签名是public final class club.throwable.enumeration.PhoneOsEnum extends java.lang.Enum<club.throwable.enumeration.PhoneOsEnum>,它的父类是java.lang.Enum,父类的泛型就是自身club.throwable.enumeration.PhoneOsEnum。上面的字节码的可读性相对比较低,直接翻译为Java代码(当然我们不能声明一个类直接继承java.lang.Enum,这里仅仅为了说明反编译后的枚举类的原型)如下:

    public final class PhoneOsEnumeration extends Enum<PhoneOsEnumeration> {
    
    	public PhoneOsEnumeration(String name, int ordinal, Integer type, String typeName) {
    		super(name, ordinal);
    		this.type = type;
    		this.typeName = typeName;
    	}
    
    	public Integer getType() {
    		return type;
    	}
    
    	public String getTypeName() {
    		return typeName;
    	}
    
    	public static PhoneOsEnumeration[] values() {
    		return $VALUES.clone();
    	}
    
    	public static PhoneOsEnumeration valueOf(String name) {
    		return Enum.valueOf(PhoneOsEnumeration.class, name);
    	}
    	
    	private final Integer type;
    	private final String typeName;
    	public static final PhoneOsEnumeration ANDROID;
    	public static final PhoneOsEnumeration IOS;
    	private static final PhoneOsEnumeration[] $VALUES;
    
    	static {
    		ANDROID = new PhoneOsEnumeration("ANDROID", 0, 1, "android");
    		IOS = new PhoneOsEnumeration("IOS", 1, 2, "ios");
    		$VALUES = new PhoneOsEnumeration[]{ANDROID, IOS};
    	}
    }
    

    概括来说就是成员变量都是通过静态代码块声明,这里注意一点父类Enum实例化的时候需要覆盖父类构造器protected Enum(String name, int ordinal),其他方法的实现都是十分简单。

    JDK的枚举描述

    国际惯例,先看一下JavaSE-8的语言规范中JLS-8.9对枚举类型的定义和描述:

    enum-1

    感觉有点似曾相识,总结一下重要内容有以下几点:

    • 枚举的声明格式是:{ClassModifier} enum Identifier [Superinterfaces] EnumBody,ClassModifier是修饰符,Identifier是枚举的名称可以类比为类名,枚举类型可以实现接口。
    • 枚举类型不能使用abstract或者final修饰,否则会产生编译错误。
    • 枚举类型的直接超类是java.lang.Enum。
    • 枚举类型除了枚举常量定义之外没有其他实例,也就是枚举类型不能实例化。
    • 枚举类型禁用反射操作进行实例化(这个特性就是Effetive Java中推荐使用枚举实现单例的原因)。

    枚举的公共父类java.lang.Enum的源码如下(已经去掉全部注释):

    public abstract class Enum<E extends Enum<E>>
            implements Comparable<E>, Serializable {
    
        private final String name; 
    
        public final String name() {
            return name;
        } 
    
        private final int ordinal;
    
        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() && // optimization
                self.getDeclaringClass() != other.getDeclaringClass())
                throw new ClassCastException();
            return self.ordinal - other.ordinal;
        }
    
        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);
        }  
    
        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");
        }                              
    }            
    

    大部分方法都比较简单,值得注意的几点是:

    • 1、valueOf方法依赖到的Class<?>#enumConstantDirectory(),这个方法首次调用完成之后,结果会缓存在Class<?>#enumConstantDirectory变量中。
    • 2、Enum实现了Serializable接口,但是readObjectreadObjectNoData直接抛出了InvalidObjectException异常,注释说到是"防止默认的反序列化",这一点有点不明不白,既然禁用反序列化为何要实现Serializable接口,这里可能考虑到是否实现Serializable接口应该交给开发者决定。
    • 3、Enum禁用克隆。

    小结

    JDK中枚举的底层实现就是使用了enum关键字声明的枚举类编译后最终会变成public final修饰同时实现了泛型接口java.lang.Enum并且指定泛型参数为自身的普通Java类,而成员属性和方法实现相关都是在编译完成后就已经成型的,枚举类型的成员变量都是通过静态代码块声明的。

    (本文完 c-1-d e-20181006)

    技术公众号(《Throwable文摘》),不定期推送笔者原创技术文章(绝不抄袭或者转载):

    娱乐公众号(《天天沙雕》),甄选奇趣沙雕图文和视频不定期推送,缓解生活工作压力:

  • 相关阅读:
    SQL中top使用方法
    event.keyCode 事件属性
    A Novel Multi-label Classification Based on PCA and ML-KNN
    Android 浏览器开发WebView setBlockNetworkImage本末
    ASIHTTPRequest 对GET POST 请求简包
    九度OJ 1177 查找 (模拟)
    【Java GUI】Java GUI基金会
    iOS开展-clang: error: unknown argument: &#39;-websockets&#39;解决方案
    java学习笔记2015-6-6
    oracle 选择最频繁出现之前,5文章数据
  • 原文地址:https://www.cnblogs.com/throwable/p/9748911.html
Copyright © 2011-2022 走看看