1. 程式人生 > 其它 >Java列舉類之Enum原始碼分析

Java列舉類之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最常用的使用方式了。

二、原始碼分析

public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable {

    //列舉常量的名稱
	//大多數程式設計師應該使用toString方法而不是訪問此欄位。
    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;
    }

    //果指定的物件等於此列舉常量,則返回true。
    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;
    }

    //返回與此列舉常量的列舉型別相對應的Class物件
    @SuppressWarnings("unchecked")
    public final Class<E> getDeclaringClass() {
        Class<?> clazz = getClass();
        Class<?> zuper = clazz.getSuperclass();
        return (zuper == java.lang.Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
    }

    /**
     * 返回具有指定名稱的指定列舉型別的列舉常量。
     * 該名稱必須與用於宣告此型別的列舉常量的識別符號完全一致。
     * 請注意,對於特定列舉型別T ,
     *  有兩個隱式宣告方法可以直接使用:
     *      public static T valueOf(String)    根據名稱獲取單個列舉型別
     *      public static T[] values()   獲取所有列舉型別陣列
     */
    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);
    }

    //列舉類不能有 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");
    }
}

案例

例1

@Getter
public enum DelFlagEnum {

    NO(0, "未刪除"),
    YES(1, "已刪除"),;

    Integer code;
    String name;

    DelFlagEnum(Integer code, String name) {
        this.code = code;
        this.name = name;
    }

    public static DelFlagEnum getCode(Integer code) {
        for (DelFlagEnum value : DelFlagEnum.values()) {
            if(value.getCode().equals(code)) {
                return value;
            }
        }
        return null;
    }
}

例2

@Getter
public enum InsuranceIndustry {
    INSURANCE_AGENT("1010", "保險代理"),
    INSURANCE_BROKER("1011", "保險經紀"),
    INSURANCE_ADJUSTMENT("1012", "保險公估"),
    PROPERTY_INSURANCE("1013", "財險公司"),
    LIFE_INSURANCE("1014", "壽險公司"),
    HEALTH_INSURANCE("1015", "健康保險"),
    ENDOWMENT_INSURANCE("1016", "養老保險"),
    ;
    String code;
    String name;

    InsuranceIndustry(String code, String name) {
        this.code = code;
        this.name = name;
    }

    // 將資料快取到map中
    private static final Map<String, String> map = new HashMap<>();

    static {
        for (InsuranceIndustry insurance : InsuranceIndustry.values()) {
            map.put(insurance.getCode(), insurance.getName());
        }
    }

    // 根據name查詢value值
    public static String getNameByCode(String code) {
        return map.get(code);
    }
}

拓展

1. 列舉是如何保證執行緒安全的

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

2. 為什麼用列舉實現的單例是最好的方式

  1. 列舉寫法簡單
  2. 列舉自己處理序列化
  3. 列舉例項建立是thread-safe(執行緒安全的)

參考文章