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\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// 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 = Utf8ANDROID #19 = Utf8Lclub/throwable/enumeration/PhoneOsEnum; #20 = Utf8IOS #21 = Utf8type #22 = Utf8Ljava/lang/Integer; #23 = Utf8typeName #24 = Utf8Ljava/lang/String; #25 = Utf8$VALUES #26 = Utf8[Lclub/throwable/enumeration/PhoneOsEnum; #27 = Utf8values #28 = Utf8()[Lclub/throwable/enumeration/PhoneOsEnum; #29 = Utf8Code #30 = Utf8LineNumberTable #31 = Utf8valueOf #32 = Utf8(Ljava/lang/String;)Lclub/throwable/enumeration/PhoneOsEnum; #33 = Utf8LocalVariableTable #34 = Utf8name #35 = Utf8<init> #36 = Utf8(Ljava/lang/String;ILjava/lang/Integer;Ljava/lang/String;)V #37 = Utf8this #38 = Utf8Signature #39 = Utf8(Ljava/lang/Integer;Ljava/lang/String;)V #40 = Utf8getType #41 = Utf8()Ljava/lang/Integer; #42 = Utf8getTypeName #43 = Utf8()Ljava/lang/String; #44 = Utf8<clinit> #45 = Utf8()V #46 = Utf8Ljava/lang/Enum<Lclub/throwable/enumeration/PhoneOsEnum;>; #47 = Utf8SourceFile #48 = Utf8PhoneOsEnum.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 = Utf8club/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 = Utf8android #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 = Utf8ios #63 = NameAndType#20:#19// IOS:Lclub/throwable/enumeration/PhoneOsEnum; #64 = Utf8java/lang/Enum #65 = Utf8clone #66 = Utf8()Ljava/lang/Object; #67 = Utf8(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; #68 = Utf8(Ljava/lang/String;I)V #69 = Utf8java/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: StartLengthSlotNameSignature 0100nameLjava/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: StartLengthSlotNameSignature 050thisLclub/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: StartLengthSlotNameSignature 050thisLclub/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; private static final PhoneOsEnumeration ANDROID; private 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的語言規範中 ofollow,noindex" target="_blank">JLS-8.9 對列舉型別的定義和描述:
感覺有點似曾相識,總結一下重要內容有以下幾點:
{ClassModifier} enum Identifier [Superinterfaces] EnumBody
列舉的公共父類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介面,但是
readObject
和readObjectNoData
直接丟擲了InvalidObjectException異常,註釋說到是"防止預設的反序列化",這一點有點不明不白,既然禁用反序列化為何要實現Serializable介面,這裡可能考慮到是否實現Serializable介面應該交給開發者決定。 - 3、Enum禁用克隆。
小結
JDK中列舉的底層實現就是使用了enum關鍵字宣告的列舉類編譯後最終會變成public final修飾同時實現了泛型介面java.lang.Enum並且指定泛型引數為自身的普通Java類,而成員屬性和方法實現相關都是在編譯完成後就已經成型的,列舉型別的成員變數都是通過靜態程式碼塊宣告的。
(本文完 c-1-d e-20181006)