天天看點

android 枚舉_java 枚舉(enum) 全面解讀簡介原理分析Enum成員變量和方法分析每一個枚舉類型極其定義的枚舉變量在JVM中都是唯一的枚舉的使用枚舉與單例模式枚舉與政策模式枚舉與Android

簡介

枚舉是Java1.5引入的新特性,通過關鍵字enum來定義枚舉類。枚舉類是一種特殊類,它和普通類一樣可以使用構造器、定義成員變量和方法,也能實作一個或多個接口,但枚舉類不能繼承其他類.

原理分析

枚舉類型使用的最常用類型就是枚舉常量.下面通過一個簡單的Demo來說明枚舉的原理.

android 枚舉_java 枚舉(enum) 全面解讀簡介原理分析Enum成員變量和方法分析每一個枚舉類型極其定義的枚舉變量在JVM中都是唯一的枚舉的使用枚舉與單例模式枚舉與政策模式枚舉與Android

這樣隻是能夠知道枚舉簡單的使用方法,不能看出枚舉的特點和枚舉的具體實作.

下面我們通過 jad工具來反編譯Color類, 通過jad -sjava Color.class反編譯出一份java檔案.

android 枚舉_java 枚舉(enum) 全面解讀簡介原理分析Enum成員變量和方法分析每一個枚舉類型極其定義的枚舉變量在JVM中都是唯一的枚舉的使用枚舉與單例模式枚舉與政策模式枚舉與Android

從反編譯的類中,可以看出, 我們使用enum關鍵字編寫的類,在編譯階段編譯器會自動幫我們生成一份真正在jvm中運作的代碼.

該類繼承自 Enum類,public abstract class Enum>implements Comparable, Serializable.

Enum類接受一個繼承自Enum的泛型.(在反編譯java檔案中沒有展現泛型是因為,泛型在階段就會被類型類型擦除,替換為具體的實作.).

從反編譯的Color類中可以看出,在enum關鍵字的類中,第一行 (準确的說是第一個分号前)定義的變量,都會生成一個 Color執行個體,且它是在靜态域中進行初始化的, 而靜态域在類加載階段的cinit中進行初始化,是以枚舉對象是線程安全的,由JVM來保證.

生成的枚舉類有 Color $VALUES[];成員變量,外部可以通過values()方法擷取目前枚舉類的所有執行個體對象.

Enum成員變量和方法分析

android 枚舉_java 枚舉(enum) 全面解讀簡介原理分析Enum成員變量和方法分析每一個枚舉類型極其定義的枚舉變量在JVM中都是唯一的枚舉的使用枚舉與單例模式枚舉與政策模式枚舉與Android

Enum成員變量

Enum成員變量和方法

Enum類實作了 Comparable接口,表明它是支援排序的,可以通過 Collections.sort 進行自動排序.實作了public final int compareTo(E o)接口,方法定義為final且其實作依賴的ordinal字段也是final類型,說明他隻能根據ordinal排序,排序規則不可變.

android 枚舉_java 枚舉(enum) 全面解讀簡介原理分析Enum成員變量和方法分析每一個枚舉類型極其定義的枚舉變量在JVM中都是唯一的枚舉的使用枚舉與單例模式枚舉與政策模式枚舉與Android

ordinal: 表示枚舉的順序,從Color類中可以看出,它是從0開始按自然數順序增長,且其值是final類型,外部無法更改.對于 ordinal()方法,官方建議盡量不要使用它,它主要是提供給EnumMap,EnumSet使用的.

android 枚舉_java 枚舉(enum) 全面解讀簡介原理分析Enum成員變量和方法分析每一個枚舉類型極其定義的枚舉變量在JVM中都是唯一的枚舉的使用枚舉與單例模式枚舉與政策模式枚舉與Android

name: 表示枚舉類的名字,從Color類的構造函數可以看出,它的值就是我們定義的執行個體的名稱.

我們在例子中之是以能列印出執行個體名稱,是因為 它的toString()方法直接傳回了name屬性.

android 枚舉_java 枚舉(enum) 全面解讀簡介原理分析Enum成員變量和方法分析每一個枚舉類型極其定義的枚舉變量在JVM中都是唯一的枚舉的使用枚舉與單例模式枚舉與政策模式枚舉與Android

equals(): 從其實作來看, 我們程式中使用 == 或者 equals來判斷兩個枚舉相等都是一樣的.

android 枚舉_java 枚舉(enum) 全面解讀簡介原理分析Enum成員變量和方法分析每一個枚舉類型極其定義的枚舉變量在JVM中都是唯一的枚舉的使用枚舉與單例模式枚舉與政策模式枚舉與Android

getDeclaringClass(): 方法傳回枚舉聲明的Class對象

每一個枚舉類型極其定義的枚舉變量在JVM中都是唯一的

這句話的意思是枚舉類型它擁有的執行個體在編寫的時候,就已經确定下,不能通過其他手段進行建立,且枚舉變量在jvm有且隻有一個對應的執行個體.

為了達到這個效果,它通過以下方法來確定.

1. 類加載時建立,保證線程安全

從Color類中可以看出, Color對象是在靜态域建立,由類加載時初始化,JVM保證線程安全,這樣就能確定Color對象不會因為并發同時請求而錯誤的建立多個執行個體.

2. 對序列化進行特殊處理,防止反序列化時建立新的對象

我們知道一旦實作了Serializable接口之後,反序列化時每次調用 readObject()方法傳回的都是一個新建立出來的對象.

而枚舉則不同,在序列化的時候Java僅僅是将枚舉對象的name屬性輸出到結果中,反序列化的時候則是通過Enum的valueOf()方法來根據名字查找枚舉對象。同時,編譯器是不允許任何對這種序列化進行定制,是以禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。

android 枚舉_java 枚舉(enum) 全面解讀簡介原理分析Enum成員變量和方法分析每一個枚舉類型極其定義的枚舉變量在JVM中都是唯一的枚舉的使用枚舉與單例模式枚舉與政策模式枚舉與Android

1.私有構造函數, 無法正常的 new出對象

android 枚舉_java 枚舉(enum) 全面解讀簡介原理分析Enum成員變量和方法分析每一個枚舉類型極其定義的枚舉變量在JVM中都是唯一的枚舉的使用枚舉與單例模式枚舉與政策模式枚舉與Android

2.無法通過 clone()方法,克隆對象

android 枚舉_java 枚舉(enum) 全面解讀簡介原理分析Enum成員變量和方法分析每一個枚舉類型極其定義的枚舉變量在JVM中都是唯一的枚舉的使用枚舉與單例模式枚舉與政策模式枚舉與Android

3. 無法通過反射的方式建立枚舉對象

枚舉類型,在 JVM 層面禁止了通過反射構造枚舉執行個體的行為,如果嘗試通過反射建立,将會報Cannot reflectively create enum objects.

android 枚舉_java 枚舉(enum) 全面解讀簡介原理分析Enum成員變量和方法分析每一個枚舉類型極其定義的枚舉變量在JVM中都是唯一的枚舉的使用枚舉與單例模式枚舉與政策模式枚舉與Android

枚舉類的特點總結

  1. 枚舉執行個體必須在 enum關鍵字聲明的類中顯式的指定(首行開始的以第一個分号結束)
  2. 除了1, 沒有任何方式(new,clone,反射,序列化)可以手動建立枚舉執行個體
  3. 枚舉類不可被繼承
  4. 枚舉類是線程安全的
  5. 枚舉類型是類型安全的(typesafe)
  6. 無法繼承其他類(已經預設繼承Enum)

枚舉的使用

  • 枚舉常量

如上訴 Color枚舉類,就是典型的枚舉常量.

它可以在 switch語句中使用

android 枚舉_java 枚舉(enum) 全面解讀簡介原理分析Enum成員變量和方法分析每一個枚舉類型極其定義的枚舉變量在JVM中都是唯一的枚舉的使用枚舉與單例模式枚舉與政策模式枚舉與Android

枚舉類型是類型安全的,可以對傳入的值進行類型檢查:

如有個 handleColor(Color color)方法,那麼方法參數自動會對類型進行檢查,隻能傳入 Color.WHITE和Color.BLACK,如果使用 static final定義的常量則不具備 類型安全的特點.

  • 枚舉與構造函數

枚舉類可以編寫自己的構造函數,但是不能聲明public,protected,為了是不讓外部建立執行個體對象,預設為private且隻能為它.

  • 枚舉與類

除了枚舉常量外, enum是一個完整的類,它也可以編寫自己的構造方法以及方法,甚至實作接口.

這裡需要注意,枚舉類不能繼承其他類,因為在編譯時它已經繼承了 Enum,java無法多繼承

android 枚舉_java 枚舉(enum) 全面解讀簡介原理分析Enum成員變量和方法分析每一個枚舉類型極其定義的枚舉變量在JVM中都是唯一的枚舉的使用枚舉與單例模式枚舉與政策模式枚舉與Android

枚舉與單例模式

單例模式網上有6-7中寫法,除了 枚舉方式外, 都有兩個緻命的缺點, 不能完全保證單例在jvm中保持唯一性.

1. 反射建立單例對象

解決方案 : 在構造上述中判斷,當多于一個執行個體時,再調用構造函數,直接報錯.

2. 反序列化時建立對象

解決方案 : 使用readResolve()方法來避免此事發生.

這兩種缺點雖然都有方式解決,但是不免有些繁瑣.

枚舉類天生有這些特性.而且實作單例相當簡單.

android 枚舉_java 枚舉(enum) 全面解讀簡介原理分析Enum成員變量和方法分析每一個枚舉類型極其定義的枚舉變量在JVM中都是唯一的枚舉的使用枚舉與單例模式枚舉與政策模式枚舉與Android

是以,枚舉實作的單例,可以說是最完美和簡潔的單例了.推薦大家使用這種方式建立單例.

但是,枚舉類的裝載和初始化時會有時間和空間的成本. 它的實作比其他方式需要更多的記憶體空間,是以在Android這種受資源限制的裝置中盡量避免使用枚舉單例,而選擇 雙重檢查鎖(DCL)和靜态内部類的方式實作單例.

枚舉與政策模式

特定的常量類型與主體中的方法或行為有關時,即當資料與行為之間有關聯時,可以考慮使用枚舉來實作政策模式.

如我們需要實作加減運算,就可以在枚舉類型中聲明一個 apply抽象方法,在特定于常量的方法(Constant-specific class body的Constant -specific method implementation)中,用具體實作抽象方法.

android 枚舉_java 枚舉(enum) 全面解讀簡介原理分析Enum成員變量和方法分析每一個枚舉類型極其定義的枚舉變量在JVM中都是唯一的枚舉的使用枚舉與單例模式枚舉與政策模式枚舉與Android

枚舉與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

但是,一定不能使用枚舉嗎?

我覺得并不如此,當資料和行為有關聯時,或者說資料受到行為的控制時,可以考慮使用政策枚舉.

繼續閱讀