前提 上一篇文章复习介绍了JDK中注解的底层实现,跟注解一样比较常用,但是底层实现比较神秘的还有枚举类型。趁着国庆假期的最后两天,把JDK中枚举的底层实现也进行一次探究。
通过例子查找本质 在探究JDK注解的底层实现的时候,因为预先参考了不少资料,所以整个过程有点”未卜先知”的意味,这里尝试用未知的角度去看注解的底层实现。先定义一个手机操作系统类型枚举PhoneOsEnum
:
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的反编译工具反编译出其字节码,执行下面的命令:
javap -c -v D:\Projects\rxjava-seed\target\classes\club\throwable\enumeration\PhoneOsEnum.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 #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
,这里仅仅为了说明反编译后的枚举类的原型)如下:
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
的源码如下(已经去掉全部注释):
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修复排版)