前提
上一篇文章復(fù)習(xí)介紹了JDK中注解的底層實(shí)現(xiàn)通孽,跟注解一樣比較常用序宦,但是底層實(shí)現(xiàn)比較神秘的還有枚舉類型。趁著國慶假期的最后兩天背苦,把JDK中枚舉的底層實(shí)現(xiàn)也進(jìn)行一次探究互捌。
通過例子查找本質(zhì)
在探究JDK注解的底層實(shí)現(xiàn)的時(shí)候,因?yàn)轭A(yù)先參考了不少資料行剂,所以整個(gè)過程有點(diǎn)"未卜先知"的意味秕噪,這里嘗試用未知的角度去看注解的底層實(shí)現(xiàn)。先定義一個(gè)手機(jī)操作系統(tǒng)類型枚舉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;
}
}
這是一個(gè)很簡單的枚舉厚宰,接著使用JDK的反編譯工具反編譯出其字節(jié)碼腌巾,執(zhí)行下面的命令:
javap -c -v D:\Projects\rxjava-seed\target\classes\club\throwable\enumeration\PhoneOsEnum.class
然后就得到了關(guān)于PhoneOsEnum.class的很長的字節(jié)碼,這里全部貼出來:
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。上面的字節(jié)碼的可讀性相對比較低撵幽,直接翻譯為Java代碼(當(dāng)然我們不能聲明一個(gè)類直接繼承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};
}
}
概括來說就是成員變量都是通過靜態(tài)代碼塊聲明,這里注意一點(diǎn)父類Enum實(shí)例化的時(shí)候需要覆蓋父類構(gòu)造器protected Enum(String name, int ordinal)
并齐,其他方法的實(shí)現(xiàn)都是十分簡單漏麦。
JDK的枚舉描述
國際慣例,先看一下JavaSE-8的語言規(guī)范中JLS-8.9對枚舉類型的定義和描述:
感覺有點(diǎn)似曾相識况褪,總結(jié)一下重要內(nèi)容有以下幾點(diǎn):
- 枚舉的聲明格式是:
{ClassModifier} enum Identifier [Superinterfaces] EnumBody
撕贞,ClassModifier是修飾符,Identifier是枚舉的名稱可以類比為類名测垛,枚舉類型可以實(shí)現(xiàn)接口捏膨。 - 枚舉類型不能使用abstract或者final修飾,否則會產(chǎn)生編譯錯(cuò)誤。
- 枚舉類型的直接超類是java.lang.Enum号涯。
- 枚舉類型除了枚舉常量定義之外沒有其他實(shí)例目胡,也就是枚舉類型不能實(shí)例化。
- 枚舉類型禁用反射操作進(jìn)行實(shí)例化(這個(gè)特性就是Effetive Java中推薦使用枚舉實(shí)現(xiàn)單例的原因)链快。
枚舉的公共父類java.lang.Enum的源碼如下(已經(jīng)去掉全部注釋):
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");
}
}
大部分方法都比較簡單誉己,值得注意的幾點(diǎn)是:
- 1、
valueOf
方法依賴到的Class<?>#enumConstantDirectory()
域蜗,這個(gè)方法首次調(diào)用完成之后巨双,結(jié)果會緩存在Class<?>#enumConstantDirectory
變量中。 - 2霉祸、Enum實(shí)現(xiàn)了Serializable接口,但是
readObject
和readObjectNoData
直接拋出了InvalidObjectException異常丝蹭,注釋說到是"防止默認(rèn)的反序列化"慢宗,這一點(diǎn)有點(diǎn)不明不白,既然禁用反序列化為何要實(shí)現(xiàn)Serializable接口奔穿,這里可能考慮到是否實(shí)現(xiàn)Serializable接口應(yīng)該交給開發(fā)者決定巫橄。 - 3宾舅、Enum禁用克隆。
小結(jié)
JDK中枚舉的底層實(shí)現(xiàn)就是使用了enum關(guān)鍵字聲明的枚舉類編譯后最終會變成public final修飾同時(shí)實(shí)現(xiàn)了泛型接口java.lang.Enum并且指定泛型參數(shù)為自身的普通Java類彩倚,而成員屬性和方法實(shí)現(xiàn)相關(guān)都是在編譯完成后就已經(jīng)成型的帆离,枚舉類型的成員變量都是通過靜態(tài)代碼塊聲明的蔬蕊。
(本文完 c-1-d e-20181006)