Enum源碼解析

  • 2019 年 10 月 5 日
  • 筆記

版權聲明:本文為部落客原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。

本文鏈接:https://blog.csdn.net/qq_37933685/article/details/80951000

個人部落格:https://suveng.github.io/blog/​​​​​​​

Enum源碼解析

引言

枚舉類型是 JDK 5 之後引進的一種非常重要的引用類型,可以用來定義一系列枚舉常量。

在沒有引入 enum 關鍵字之前,要表示可枚舉的變數,只能使用 public static final 的方式。

public staic final int SPRING = 1;  public staic final int SUMMER = 2;  public staic final int AUTUMN = 3;  public staic final int WINTER = 4;

這種實現方式有幾個弊端。首先,類型不安全。試想一下,有一個方法期待接受一個季節作為參數,那麼只能將參數類型聲明為 int,但是傳入的值可能是 99。顯然只能在運行時進行參數合理性的判斷,無法在編譯期間完成檢查。其次,指意性不強,含義不明確。我們使用枚舉,很多場合會用到該枚舉的字串符表達,而上述的實現中只能得到一個數字,不能直觀地表達該枚舉常量的含義。當然也可用 String 常量,但是又會帶來性能問題,因為比較要依賴字元串的比較操作。

使用 enum 來表示枚舉可以更好地保證程式的類型安全和可讀性。

enum 是類型安全的。除了預先定義的枚舉常量,不能將其它的值賦給枚舉變數。這和用 int 或 String 實現的枚舉很不一樣。

enum 有自己的名稱空間,且可讀性強。在創建 enum 時,編譯器會自動添加一些有用的特性。每個 enum 實例都有一個名字 (name) 和一個序號 (ordinal),可以通過 toString() 方法獲取 enum 實例的字元串表示。還以通過 values() 方法獲得一個由 enum 常量按順序構成的數組。

enum 還有一個特別實用的特性,可以在 switch 語句中使用,這也是 enum 最常用的使用方式了。

反編譯枚舉類型源碼

源碼解析

注意:解析是放再源碼裡面的注釋

環境:jdk1.8

Enum的類圖

package java.lang;    import java.io.Serializable;  import java.io.IOException;  import java.io.InvalidObjectException;  import java.io.ObjectInputStream;  import java.io.ObjectStreamException;    /*  聲明方法的描述,請參見The Java™ Language Specification的第8.9 節 。  請注意,當使用枚舉類型作為集合的類型或映射中的鍵的類型時,可以使用專門且高效的set和map實現。    從以下版本開始:  1.5  另請參見:  Class.getEnumConstants() , EnumSet , EnumMap , Serialized Form   */  public abstract class Enum<E extends Enum<E>>          implements Comparable<E>, Serializable {  /*  枚舉常量的名稱,如枚舉聲明中聲明的那樣。  大多數程式設計師應該使用toString方法而不是訪問此欄位。  返回:        枚舉常量的名稱   */      private final String name;    /*  返回此枚舉常量的名稱,與其枚舉聲明中聲明的完全相同。  大多數程式設計師應優先使用toString方法,因為toString方法可能返回一個更加用戶友好的名稱。  方法主要用於特殊情況,  其中正確性取決於獲取確切名稱,該名稱在不同版本之間不會有所不同。 這個枚舉常量的名稱   */      public final String name() {          return name;      }    /*  此枚舉常量的序數(它在枚舉聲明中的位置,其中初始常量的序數為零)。  大多數程式設計師都不會使用這個領域。 它設計用於複雜的基於枚舉的數據結構,、  例如java.util.EnumSet和java.util.EnumMap。   */      private final int ordinal;    /*  返回此枚舉常量的序數(它在枚舉聲明中的位置,其中初始常量的序數為零)。  大多數程式設計師都沒有使用這種方法。  它設計用於複雜的基於枚舉的數據結構,例如java.util.EnumSet和java.util.EnumMap  @return此枚舉常量的序數   */      public final int ordinal() {          return ordinal;      }        /**       * 唯一的構造函數。 程式設計師無法調用此構造函數。       * 它由編譯器發出的程式碼用於響應枚舉類型聲明。       *       * @param name - 此枚舉常量的名稱,它是用於聲明它的標識符。       * @param ordinal - 此枚舉常量的序數(它在枚舉聲明中的位置,其中初始常量的序數為零)。       */      protected Enum(String name, int ordinal) {          this.name = name;          this.ordinal = ordinal;      }        /**       * 返回聲明中包含的此枚舉常量的名稱。 可以覆蓋該方法,但通常不需要或不需要。       * 當存在更「程式設計師友好」的字元串形式時,枚舉類型應該重寫此方法。       *       * @return 這個枚舉常量的名稱       */      public String toString() {          return name;      }        /**       * 如果指定的對象等於此枚舉常量,則返回true。       *       * @param other 要與此對象進行相等性比較的對象。       * @return  如果指定的對象等於此枚舉常量,則返回true。       */      public final boolean equals(Object other) {          return this==other;      }        /**       * 返回此枚舉常量的哈希碼。       *       * @return 此枚舉常量的哈希碼。       */      public final int hashCode() {          return super.hashCode();      }        /**       * 拋出CloneNotSupportedException。       * 這保證了枚舉永遠不會被克隆,這是保持其「單身」狀態所必需的。       *       * @return (never returns)       */      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;      }        /**       * 返回與此枚舉常量的枚舉類型相對應的Class對象。       * 當且僅當e1.getDeclaringClass()== e2.getDeclaringClass())時,       * 兩個枚舉常量e1和e2具有相同的枚舉類型。       * (此方法返回的值可能與使用常量特定類體的枚舉常數Object.getClass()方法返回的值不同)       *       * @return 該類對象對應於此枚舉常量的枚舉類型       */      @SuppressWarnings("unchecked")      public final Class<E> getDeclaringClass() {          Class<?> clazz = getClass();          Class<?> zuper = clazz.getSuperclass();          return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;      }        /**       * 返回具有指定名稱的指定枚舉類型的枚舉常量。       * 該名稱必須與用於聲明此類型的枚舉常量的標識符完全一致。       * (不允許使用外來空白字元。)       *       * 請注意,對於特定枚舉類型T ,       * 可以使用該枚舉上隱式聲明的public static T valueOf(String)方法,       * 而不是使用此方法將名稱映射到相應的枚舉常量。       * 枚舉類型的所有常量可以通過調用該類型的隱式public static T[] values()方法來獲得。       * @param <T> T - 要返回其常量的枚舉類型       * @param enumType  類返回常量的枚舉類型的 類對象         * @param name 常量返回的名稱       * @return 具有指定名稱的指定枚舉類型的枚舉常量       * @throws IllegalArgumentException 如果指定的枚舉類型沒有指定名稱的常量,或者指定的類對象不表示枚舉類型         * @throws NullPointerException 如果 enumType或 name為null         * @since 1.5       */      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);      }      /*      可以看到,enumType.enumConstantDirectory().get(name);enumType這個枚舉類抵用enumConstantDirectory方法      拿到這個枚舉類的map,程式碼如下。然後根據name拿到這個枚舉的對象。      Map<String, T> enumConstantDirectory() {          if (enumConstantDirectory == null) {              T[] universe = getEnumConstantsShared();              if (universe == null)                  throw new IllegalArgumentException(                      getName() + " is not an enum type");              Map<String, T> m = new HashMap<>(2 * universe.length);              for (T constant : universe)                  m.put(((Enum<?>)constant).name(), constant);              enumConstantDirectory = m;          }          return enumConstantDirectory;      }      enumConstantDirectory()方法獲取枚舉常量目錄,沒有就繼續調用getEnumConstantsShared();如果有返回值,就遍歷put進enumConstantDirectory。然後返回。方法說明如下      getEnumConstantsShared();返回此枚舉類的元素,如果此Class對象不表示枚舉類型,則返回null;      與getEnumConstants相同,但結果是由所有調用者取消克隆,快取和共享。         */        /**       * 枚舉類不能有finalize方法       */      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");      }  }

枚舉是如何保證執行緒安全的

當一個Java類第一次被真正使用到的時候靜態資源被初始化、Java類的載入和初始化過程都是執行緒安全的。所以,創建一個enum類型是執行緒安全的

為什麼用枚舉實現的單例是最好的方式

1. 枚舉寫法簡單

2. 枚舉自己處理序列化

3.枚舉實例創建是thread-safe(執行緒安全的)

參考文獻

http://blog.jobbole.com/94074/

http://blog.jrwang.me/2016/java-enum/