前提 上一篇文章复习介绍了JDK中注解的底层实现,跟注解一样比较常用,但是底层实现比较神秘的还有枚举类型。趁着国庆假期的最后两天,把JDK中枚举的底层实现也进行一次探究。
通过例子查找本质 在探究JDK注解的底层实现的时候,因为预先参考了不少资料,所以整个过程有点”未卜先知”的意味,这里尝试用未知的角度去看注解的底层实现。先定义一个手机操作系统类型枚举PhoneOsEnum
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package club.throwable.enumeration;public enum PhoneOsEnum { ANDROID(1 , "android" ), 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的反编译工具反编译出其字节码,执行下面的命令:
1 javap -c -v D:\Projects\rxjava-seed\target\classes\club\throwable\enumeration\PhoneOsEnum.class
然后就得到了关于PhoneOsEnum.class
的很长的字节码,这里全部贴出来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 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 #2 = Methodref #50. #51 #3 = Class #26 #4 = Class #52 #5 = Methodref #17. #53 #6 = Methodref #17. #54 #7 = Fieldref #4. #55 #8 = Fieldref #4. #56 #9 = String #18 #10 = Methodref #57. #58 #11 = String #59 #12 = Methodref #4. #60 #13 = Fieldref #4. #61 #14 = String #20 #15 = String #62 #16 = Fieldref #4. #63 #17 = Class #64 #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 #50 = Class #26 #51 = NameAndType #65 :#66 #52 = Utf8 club/throwable/enumeration/PhoneOsEnum #53 = NameAndType #31 :#67 #54 = NameAndType #35 :#68 #55 = NameAndType #21 :#22 #56 = NameAndType #23 :#24 #57 = Class #69 #58 = NameAndType #31 :#70 #59 = Utf8 android #60 = NameAndType #35 :#36 #61 = NameAndType #18 :#19 #62 = Utf8 ios #63 = NameAndType #20 :#19 #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 3 : invokevirtual #2 6 : checkcast #3 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 2 : aload_0 3 : invokestatic #5 6 : checkcast #4 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 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 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 3 : dup 4 : ldc #9 6 : iconst_0 7 : iconst_1 8 : invokestatic #10 11 : ldc #11 13 : invokespecial #12 16 : putstatic #13 19 : new #4 22 : dup 23 : ldc #14 25 : iconst_1 26 : iconst_2 27 : invokestatic #10 30 : ldc #15 32 : invokespecial #12 35 : putstatic #16 38 : iconst_2 39 : anewarray #4 42 : dup 43 : iconst_0 44 : getstatic #13 47 : aastore 48 : dup 49 : iconst_1 50 : getstatic #16 53 : aastore 54 : putstatic #1 57 : return LineNumberTable: line 14 : 0 line 19 : 19 line 9 : 38 } Signature: #46 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
,这里仅仅为了说明反编译后的枚举类的原型)如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 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 对枚举类型的定义和描述:
感觉有点似曾相识,总结一下重要内容有以下几点:
枚举的声明格式是:{ClassModifier} enum Identifier [Superinterfaces] EnumBody
,ClassModifier
是修饰符,Identifier
是枚举的名称可以类比为类名,枚举类型可以实现接口。 枚举类型不能使用abstract
或者final
修饰,否则会产生编译错误。 枚举类型的直接超类是java.lang.Enum
。 枚举类型除了枚举常量定义之外没有其他实例,也就是枚举类型不能实例化。 枚举类型禁用反射操作进行实例化(这个特性就是Effetive Java
中推荐使用枚举实现单例的原因)。 枚举的公共父类java.lang.Enum
的源码如下(已经去掉全部注释):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 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() && 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
接口,但是readObject
和readObjectNoData
直接抛出了InvalidObjectException
异常,注释说到是”防止默认的反序列化”,这一点有点不明不白,既然禁用反序列化为何要实现Serializable
接口,这里可能考虑到是否实现Serializable
接口应该交给开发者决定。 3、Enum
禁用克隆。 小结 JDK
中枚举的底层实现就是使用了enum
关键字声明的枚举类编译后最终会变成public final
修饰同时实现了继承了泛型抽象类java.lang.Enum
并且指定泛型参数为自身的普通Java
类,而成员属性和方法实现相关都是在编译完成后就已经成型的,枚举类型的成员变量都是通过静态代码块声明的。
(本文完 c-1-d e-20181006 r-a-20200412修复排版)