前提
上一篇文章复习介绍了JDK中注解的底层实现,跟注解一样比较常用,但是底层实现比较神秘的还有枚举类型。趁着国庆假期的最后两天,把JDK中枚举的底层实现也进行一次探究。
通过例子查找本质
在探究JDK注解的底层实现的时候,因为预先参考了不少资料,所以整个过程有点”未卜先知”的意味,这里尝试用未知的角度去看注解的底层实现。先定义一个手机操作系统类型枚举PhoneOsEnum
:
1package club.throwable.enumeration;
2
3public enum PhoneOsEnum {
4
5 /**
6 * 安卓
7 */
8 ANDROID(1, "android"),
9
10 /**
11 * ios
12 */
13 IOS(2, "ios");
14
15
16 private final Integer type;
17 private final String typeName;
18
19 PhoneOsEnum(Integer type, String typeName) {
20 this.type = type;
21 this.typeName = typeName;
22 }
23
24 public Integer getType() {
25 return type;
26 }
27
28 public String getTypeName() {
29 return typeName;
30 }
31}
这是一个很简单的枚举,接着使用JDK的反编译工具反编译出其字节码,执行下面的命令:
1javap -c -v D:Projectsrxjava-seedtargetclassesclubthrowableenumerationPhoneOsEnum.class
然后就得到了关于PhoneOsEnum.class
的很长的字节码,这里全部贴出来:
1Classfile /D:/Projects/rxjava-seed/target/classes/club/throwable/enumeration/PhoneOsEnum.class
2 Last modified 2018-10-6; size 1561 bytes
3 MD5 checksum 6d3186042f54233219000927a2f196aa
4 Compiled from "PhoneOsEnum.java"
5public final class club.throwable.enumeration.PhoneOsEnum extends java.lang.Enum<club.throwable.enumeration.PhoneOsEnum>
6 minor version: 0
7 major version: 52
8 flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
9Constant pool:
10 #1 = Fieldref #4.#49 // club/throwable/enumeration/PhoneOsEnum.$VALUES:[Lclub/throwable/enumeration/PhoneOsEnum;
11 #2 = Methodref #50.#51 // "[Lclub/throwable/enumeration/PhoneOsEnum;".clone:()Ljava/lang/Object;
12 #3 = Class #26 // "[Lclub/throwable/enumeration/PhoneOsEnum;"
13 #4 = Class #52 // club/throwable/enumeration/PhoneOsEnum
14 #5 = Methodref #17.#53 // java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
15 #6 = Methodref #17.#54 // java/lang/Enum."<init>":(Ljava/lang/String;I)V
16 #7 = Fieldref #4.#55 // club/throwable/enumeration/PhoneOsEnum.type:Ljava/lang/Integer;
17 #8 = Fieldref #4.#56 // club/throwable/enumeration/PhoneOsEnum.typeName:Ljava/lang/String;
18 #9 = String #18 // ANDROID
19 #10 = Methodref #57.#58 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
20 #11 = String #59 // android
21 #12 = Methodref #4.#60 // club/throwable/enumeration/PhoneOsEnum."<init>":(Ljava/lang/String;ILjava/lang/Integer;Ljava/lang/String;)V
22 #13 = Fieldref #4.#61 // club/throwable/enumeration/PhoneOsEnum.ANDROID:Lclub/throwable/enumeration/PhoneOsEnum;
23 #14 = String #20 // IOS
24 #15 = String #62 // ios
25 #16 = Fieldref #4.#63 // club/throwable/enumeration/PhoneOsEnum.IOS:Lclub/throwable/enumeration/PhoneOsEnum;
26 #17 = Class #64 // java/lang/Enum
27 #18 = Utf8 ANDROID
28 #19 = Utf8 Lclub/throwable/enumeration/PhoneOsEnum;
29 #20 = Utf8 IOS
30 #21 = Utf8 type
31 #22 = Utf8 Ljava/lang/Integer;
32 #23 = Utf8 typeName
33 #24 = Utf8 Ljava/lang/String;
34 #25 = Utf8 $VALUES
35 #26 = Utf8 [Lclub/throwable/enumeration/PhoneOsEnum;
36 #27 = Utf8 values
37 #28 = Utf8 ()[Lclub/throwable/enumeration/PhoneOsEnum;
38 #29 = Utf8 Code
39 #30 = Utf8 LineNumberTable
40 #31 = Utf8 valueOf
41 #32 = Utf8 (Ljava/lang/String;)Lclub/throwable/enumeration/PhoneOsEnum;
42 #33 = Utf8 LocalVariableTable
43 #34 = Utf8 name
44 #35 = Utf8 <init>
45 #36 = Utf8 (Ljava/lang/String;ILjava/lang/Integer;Ljava/lang/String;)V
46 #37 = Utf8 this
47 #38 = Utf8 Signature
48 #39 = Utf8 (Ljava/lang/Integer;Ljava/lang/String;)V
49 #40 = Utf8 getType
50 #41 = Utf8 ()Ljava/lang/Integer;
51 #42 = Utf8 getTypeName
52 #43 = Utf8 ()Ljava/lang/String;
53 #44 = Utf8 <clinit>
54 #45 = Utf8 ()V
55 #46 = Utf8 Ljava/lang/Enum<Lclub/throwable/enumeration/PhoneOsEnum;>;
56 #47 = Utf8 SourceFile
57 #48 = Utf8 PhoneOsEnum.java
58 #49 = NameAndType #25:#26 // $VALUES:[Lclub/throwable/enumeration/PhoneOsEnum;
59 #50 = Class #26 // "[Lclub/throwable/enumeration/PhoneOsEnum;"
60 #51 = NameAndType #65:#66 // clone:()Ljava/lang/Object;
61 #52 = Utf8 club/throwable/enumeration/PhoneOsEnum
62 #53 = NameAndType #31:#67 // valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
63 #54 = NameAndType #35:#68 // "<init>":(Ljava/lang/String;I)V
64 #55 = NameAndType #21:#22 // type:Ljava/lang/Integer;
65 #56 = NameAndType #23:#24 // typeName:Ljava/lang/String;
66 #57 = Class #69 // java/lang/Integer
67 #58 = NameAndType #31:#70 // valueOf:(I)Ljava/lang/Integer;
68 #59 = Utf8 android
69 #60 = NameAndType #35:#36 // "<init>":(Ljava/lang/String;ILjava/lang/Integer;Ljava/lang/String;)V
70 #61 = NameAndType #18:#19 // ANDROID:Lclub/throwable/enumeration/PhoneOsEnum;
71 #62 = Utf8 ios
72 #63 = NameAndType #20:#19 // IOS:Lclub/throwable/enumeration/PhoneOsEnum;
73 #64 = Utf8 java/lang/Enum
74 #65 = Utf8 clone
75 #66 = Utf8 ()Ljava/lang/Object;
76 #67 = Utf8 (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
77 #68 = Utf8 (Ljava/lang/String;I)V
78 #69 = Utf8 java/lang/Integer
79 #70 = Utf8 (I)Ljava/lang/Integer;
80{
81 public static final club.throwable.enumeration.PhoneOsEnum ANDROID;
82 descriptor: Lclub/throwable/enumeration/PhoneOsEnum;
83 flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
84
85 public static final club.throwable.enumeration.PhoneOsEnum IOS;
86 descriptor: Lclub/throwable/enumeration/PhoneOsEnum;
87 flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
88
89 public static club.throwable.enumeration.PhoneOsEnum[] values();
90 descriptor: ()[Lclub/throwable/enumeration/PhoneOsEnum;
91 flags: ACC_PUBLIC, ACC_STATIC
92 Code:
93 stack=1, locals=0, args_size=0
94 0: getstatic #1 // Field $VALUES:[Lclub/throwable/enumeration/PhoneOsEnum;
95 3: invokevirtual #2 // Method "[Lclub/throwable/enumeration/PhoneOsEnum;".clone:()Ljava/lang/Object;
96 6: checkcast #3 // class "[Lclub/throwable/enumeration/PhoneOsEnum;"
97 9: areturn
98 LineNumberTable:
99 line 9: 0
100
101 public static club.throwable.enumeration.PhoneOsEnum valueOf(java.lang.String);
102 descriptor: (Ljava/lang/String;)Lclub/throwable/enumeration/PhoneOsEnum;
103 flags: ACC_PUBLIC, ACC_STATIC
104 Code:
105 stack=2, locals=1, args_size=1
106 0: ldc #4 // class club/throwable/enumeration/PhoneOsEnum
107 2: aload_0
108 3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
109 6: checkcast #4 // class club/throwable/enumeration/PhoneOsEnum
110 9: areturn
111 LineNumberTable:
112 line 9: 0
113 LocalVariableTable:
114 Start Length Slot Name Signature
115 0 10 0 name Ljava/lang/String;
116
117 public java.lang.Integer getType();
118 descriptor: ()Ljava/lang/Integer;
119 flags: ACC_PUBLIC
120 Code:
121 stack=1, locals=1, args_size=1
122 0: aload_0
123 1: getfield #7 // Field type:Ljava/lang/Integer;
124 4: areturn
125 LineNumberTable:
126 line 31: 0
127 LocalVariableTable:
128 Start Length Slot Name Signature
129 0 5 0 this Lclub/throwable/enumeration/PhoneOsEnum;
130
131 public java.lang.String getTypeName();
132 descriptor: ()Ljava/lang/String;
133 flags: ACC_PUBLIC
134 Code:
135 stack=1, locals=1, args_size=1
136 0: aload_0
137 1: getfield #8 // Field typeName:Ljava/lang/String;
138 4: areturn
139 LineNumberTable:
140 line 35: 0
141 LocalVariableTable:
142 Start Length Slot Name Signature
143 0 5 0 this Lclub/throwable/enumeration/PhoneOsEnum;
144
145 static {};
146 descriptor: ()V
147 flags: ACC_STATIC
148 Code:
149 stack=6, locals=0, args_size=0
150 0: new #4 // class club/throwable/enumeration/PhoneOsEnum
151 3: dup
152 4: ldc #9 // String ANDROID
153 6: iconst_0
154 7: iconst_1
155 8: invokestatic #10 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
156 11: ldc #11 // String android
157 13: invokespecial #12 // Method "<init>":(Ljava/lang/String;ILjava/lang/Integer;Ljava/lang/String;)V
158 16: putstatic #13 // Field ANDROID:Lclub/throwable/enumeration/PhoneOsEnum;
159 19: new #4 // class club/throwable/enumeration/PhoneOsEnum
160 22: dup
161 23: ldc #14 // String IOS
162 25: iconst_1
163 26: iconst_2
164 27: invokestatic #10 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
165 30: ldc #15 // String ios
166 32: invokespecial #12 // Method "<init>":(Ljava/lang/String;ILjava/lang/Integer;Ljava/lang/String;)V
167 35: putstatic #16 // Field IOS:Lclub/throwable/enumeration/PhoneOsEnum;
168 38: iconst_2
169 39: anewarray #4 // class club/throwable/enumeration/PhoneOsEnum
170 42: dup
171 43: iconst_0
172 44: getstatic #13 // Field ANDROID:Lclub/throwable/enumeration/PhoneOsEnum;
173 47: aastore
174 48: dup
175 49: iconst_1
176 50: getstatic #16 // Field IOS:Lclub/throwable/enumeration/PhoneOsEnum;
177 53: aastore
178 54: putstatic #1 // Field $VALUES:[Lclub/throwable/enumeration/PhoneOsEnum;
179 57: return
180 LineNumberTable:
181 line 14: 0
182 line 19: 19
183 line 9: 38
184}
185Signature: #46 // Ljava/lang/Enum<Lclub/throwable/enumeration/PhoneOsEnum;>;
186SourceFile: "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
,这里仅仅为了说明反编译后的枚举类的原型)如下:
1public final class PhoneOsEnumeration extends Enum<PhoneOsEnumeration> {
2
3 public PhoneOsEnumeration(String name, int ordinal, Integer type, String typeName) {
4 super(name, ordinal);
5 this.type = type;
6 this.typeName = typeName;
7 }
8
9 public Integer getType() {
10 return type;
11 }
12
13 public String getTypeName() {
14 return typeName;
15 }
16
17 public static PhoneOsEnumeration[] values() {
18 return $VALUES.clone();
19 }
20
21 public static PhoneOsEnumeration valueOf(String name) {
22 return Enum.valueOf(PhoneOsEnumeration.class, name);
23 }
24
25 private final Integer type;
26 private final String typeName;
27 public static final PhoneOsEnumeration ANDROID;
28 public static final PhoneOsEnumeration IOS;
29 private static final PhoneOsEnumeration[] $VALUES;
30
31 static {
32 ANDROID = new PhoneOsEnumeration("ANDROID", 0, 1, "android");
33 IOS = new PhoneOsEnumeration("IOS", 1, 2, "ios");
34 $VALUES = new PhoneOsEnumeration[]{ANDROID, IOS};
35 }
36}
概括来说就是成员变量都是通过静态代码块声明,这里注意一点父类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
的源码如下(已经去掉全部注释):
1public abstract class Enum<E extends Enum<E>>
2 implements Comparable<E>, Serializable {
3
4 private final String name;
5
6 public final String name() {
7 return name;
8 }
9
10 private final int ordinal;
11
12 public final int ordinal() {
13 return ordinal;
14 }
15
16 protected Enum(String name, int ordinal) {
17 this.name = name;
18 this.ordinal = ordinal;
19 }
20
21 public String toString() {
22 return name;
23 }
24
25 public final boolean equals(Object other) {
26 return this==other;
27 }
28
29 public final int hashCode() {
30 return super.hashCode();
31 }
32
33 protected final Object clone() throws CloneNotSupportedException {
34 throw new CloneNotSupportedException();
35 }
36
37 public final int compareTo(E o) {
38 Enum<?> other = (Enum<?>)o;
39 Enum<E> self = this;
40 if (self.getClass() != other.getClass() && // optimization
41 self.getDeclaringClass() != other.getDeclaringClass())
42 throw new ClassCastException();
43 return self.ordinal - other.ordinal;
44 }
45
46 public final Class<E> getDeclaringClass() {
47 Class<?> clazz = getClass();
48 Class<?> zuper = clazz.getSuperclass();
49 return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
50 }
51
52 public static <T extends Enum<T>> T valueOf(Class<T> enumType,
53 String name) {
54 T result = enumType.enumConstantDirectory().get(name);
55 if (result != null)
56 return result;
57 if (name == null)
58 throw new NullPointerException("Name is null");
59 throw new IllegalArgumentException(
60 "No enum constant " + enumType.getCanonicalName() + "." + name);
61 }
62
63 protected final void finalize() { }
64
65 private void readObject(ObjectInputStream in) throws IOException,
66 ClassNotFoundException {
67 throw new InvalidObjectException("can't deserialize enum");
68 }
69
70 private void readObjectNoData() throws ObjectStreamException {
71 throw new InvalidObjectException("can't deserialize enum");
72 }
73}
大部分方法都比较简单,值得注意的几点是:
-
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修复排版)
原文始发于微信公众号(Throwable):JDK中枚举的底层实现
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/38613.html