簡介
枚舉是Java1.5引入的新特性,通過關鍵字enum來定義枚舉類。枚舉類是一種特殊類,它和普通類一樣可以使用構造器、定義成員變量和方法,也能實作一個或多個接口,但枚舉類不能繼承其他類.
原理分析
枚舉類型使用的最常用類型就是枚舉常量.下面通過一個簡單的Demo來說明枚舉的原理.
這樣隻是能夠知道枚舉簡單的使用方法,不能看出枚舉的特點和枚舉的具體實作.
下面我們通過 jad工具來反編譯Color類, 通過jad -sjava Color.class反編譯出一份java檔案.
從反編譯的類中,可以看出, 我們使用enum關鍵字編寫的類,在編譯階段編譯器會自動幫我們生成一份真正在jvm中運作的代碼.
該類繼承自 Enum類,public abstract class Enum>implements Comparable, Serializable.
Enum類接受一個繼承自Enum的泛型.(在反編譯java檔案中沒有展現泛型是因為,泛型在階段就會被類型類型擦除,替換為具體的實作.).
從反編譯的Color類中可以看出,在enum關鍵字的類中,第一行 (準确的說是第一個分号前)定義的變量,都會生成一個 Color執行個體,且它是在靜态域中進行初始化的, 而靜态域在類加載階段的cinit中進行初始化,是以枚舉對象是線程安全的,由JVM來保證.
生成的枚舉類有 Color $VALUES[];成員變量,外部可以通過values()方法擷取目前枚舉類的所有執行個體對象.
Enum成員變量和方法分析
Enum成員變量
Enum成員變量和方法
Enum類實作了 Comparable接口,表明它是支援排序的,可以通過 Collections.sort 進行自動排序.實作了public final int compareTo(E o)接口,方法定義為final且其實作依賴的ordinal字段也是final類型,說明他隻能根據ordinal排序,排序規則不可變.
ordinal: 表示枚舉的順序,從Color類中可以看出,它是從0開始按自然數順序增長,且其值是final類型,外部無法更改.對于 ordinal()方法,官方建議盡量不要使用它,它主要是提供給EnumMap,EnumSet使用的.
name: 表示枚舉類的名字,從Color類的構造函數可以看出,它的值就是我們定義的執行個體的名稱.
我們在例子中之是以能列印出執行個體名稱,是因為 它的toString()方法直接傳回了name屬性.
equals(): 從其實作來看, 我們程式中使用 == 或者 equals來判斷兩個枚舉相等都是一樣的.
getDeclaringClass(): 方法傳回枚舉聲明的Class對象
每一個枚舉類型極其定義的枚舉變量在JVM中都是唯一的
這句話的意思是枚舉類型它擁有的執行個體在編寫的時候,就已經确定下,不能通過其他手段進行建立,且枚舉變量在jvm有且隻有一個對應的執行個體.
為了達到這個效果,它通過以下方法來確定.
1. 類加載時建立,保證線程安全
從Color類中可以看出, Color對象是在靜态域建立,由類加載時初始化,JVM保證線程安全,這樣就能確定Color對象不會因為并發同時請求而錯誤的建立多個執行個體.
2. 對序列化進行特殊處理,防止反序列化時建立新的對象
我們知道一旦實作了Serializable接口之後,反序列化時每次調用 readObject()方法傳回的都是一個新建立出來的對象.
而枚舉則不同,在序列化的時候Java僅僅是将枚舉對象的name屬性輸出到結果中,反序列化的時候則是通過Enum的valueOf()方法來根據名字查找枚舉對象。同時,編譯器是不允許任何對這種序列化進行定制,是以禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。
1.私有構造函數, 無法正常的 new出對象
2.無法通過 clone()方法,克隆對象
3. 無法通過反射的方式建立枚舉對象
枚舉類型,在 JVM 層面禁止了通過反射構造枚舉執行個體的行為,如果嘗試通過反射建立,将會報Cannot reflectively create enum objects.
枚舉類的特點總結
- 枚舉執行個體必須在 enum關鍵字聲明的類中顯式的指定(首行開始的以第一個分号結束)
- 除了1, 沒有任何方式(new,clone,反射,序列化)可以手動建立枚舉執行個體
- 枚舉類不可被繼承
- 枚舉類是線程安全的
- 枚舉類型是類型安全的(typesafe)
- 無法繼承其他類(已經預設繼承Enum)
枚舉的使用
- 枚舉常量
如上訴 Color枚舉類,就是典型的枚舉常量.
它可以在 switch語句中使用
枚舉類型是類型安全的,可以對傳入的值進行類型檢查:
如有個 handleColor(Color color)方法,那麼方法參數自動會對類型進行檢查,隻能傳入 Color.WHITE和Color.BLACK,如果使用 static final定義的常量則不具備 類型安全的特點.
- 枚舉與構造函數
枚舉類可以編寫自己的構造函數,但是不能聲明public,protected,為了是不讓外部建立執行個體對象,預設為private且隻能為它.
- 枚舉與類
除了枚舉常量外, enum是一個完整的類,它也可以編寫自己的構造方法以及方法,甚至實作接口.
這裡需要注意,枚舉類不能繼承其他類,因為在編譯時它已經繼承了 Enum,java無法多繼承
枚舉與單例模式
單例模式網上有6-7中寫法,除了 枚舉方式外, 都有兩個緻命的缺點, 不能完全保證單例在jvm中保持唯一性.
1. 反射建立單例對象
解決方案 : 在構造上述中判斷,當多于一個執行個體時,再調用構造函數,直接報錯.
2. 反序列化時建立對象
解決方案 : 使用readResolve()方法來避免此事發生.
這兩種缺點雖然都有方式解決,但是不免有些繁瑣.
枚舉類天生有這些特性.而且實作單例相當簡單.
是以,枚舉實作的單例,可以說是最完美和簡潔的單例了.推薦大家使用這種方式建立單例.
但是,枚舉類的裝載和初始化時會有時間和空間的成本. 它的實作比其他方式需要更多的記憶體空間,是以在Android這種受資源限制的裝置中盡量避免使用枚舉單例,而選擇 雙重檢查鎖(DCL)和靜态内部類的方式實作單例.
枚舉與政策模式
特定的常量類型與主體中的方法或行為有關時,即當資料與行為之間有關聯時,可以考慮使用枚舉來實作政策模式.
如我們需要實作加減運算,就可以在枚舉類型中聲明一個 apply抽象方法,在特定于常量的方法(Constant-specific class body的Constant -specific method implementation)中,用具體實作抽象方法.
枚舉與Android
在舊版的Android開發者官網的指南 Managing Your App's Memory,新版中已經被移除.
有這麼一句話 :
Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.
使用枚舉常量比使用final static來實作常量定義,枚舉的記憶體消耗比後高不止兩倍. 你應該嚴格避免在Android上使用枚舉.
導緻很多開發者把它當成了教條,禁止在Android上使用枚舉.
從反編譯的Color類中可以發現, 枚舉為每一個對象建立一個枚舉對象,枚舉對象裡面至少有 一個String類型(name),和一個int類型(ordinal)再加上對象頭部占用的記憶體.(此處還忽略了$VALUS數組的建立消耗).
單個枚舉類型常量,比static final聲明的常量占用的記憶體大的多.
是以,不建議在Android中使用枚舉常量,而更偏向于使用 static final來定義常量.
但是,枚舉常量中有類型安全檢查的功能,使用正常的實作,沒有這種功能.
這裡我們可以使用android提供的注解來實作類型檢查. @StringDef和@IntDef
具體可以參考這篇文章. Android Performance: Avoid using ENUM on Android
但是,一定不能使用枚舉嗎?
我覺得并不如此,當資料和行為有關聯時,或者說資料受到行為的控制時,可以考慮使用政策枚舉.