首先給出文章的結(jié)論:
- 枚舉是類(lèi)類(lèi)型
- 每個(gè)枚舉常量都是所屬枚舉類(lèi)的對(duì)象
- 枚舉類(lèi)在加載完成后罐农,無(wú)法再進(jìn)行實(shí)例化操作
- 枚舉是線程安全的
java 中的枚舉在實(shí)現(xiàn)上非常簡(jiǎn)單趁仙,以下就是一個(gè)枚舉的例子:
public enum FruitEnum {
APPLE
}
但是如果反編譯 FruitEnum.class
耗拓,會(huì)發(fā)現(xiàn)編譯器在背后默默的做了大量的工作兔仰,以下是反編譯結(jié)果:
public final class FruitEnum extends Enum {
public static final FruitEnum APPLE;
private static final FruitEnum $VALUES[];
private FruitEnum(String s, int i) {
super(s, i);
}
public static FruitEnum[] values() {
return (FruitEnum[])$VALUES.clone();
}
public static FruitEnum valueOf(String name) {
return (FruitEnum)Enum.valueOf(FruitEnum, name);
}
static {
APPLE = new FruitEnum("APPLE", 0);
$VALUES = (new FruitEnum[] {
APPLE
});
}
}
下面根據(jù)反編譯結(jié)果說(shuō)明枚舉的幾個(gè)特性玛臂。
- 枚舉的實(shí)際類(lèi)型
public final class FruitEnum extends Enum
說(shuō)明枚舉是類(lèi),并且是用 final 修飾的類(lèi)芥映,意味著枚舉不能再被繼承擴(kuò)展溅话。而我們聲明枚舉類(lèi)時(shí)使用的 enum 只是一個(gè)關(guān)鍵字。
其次盅称,所有的枚舉都繼承了一個(gè)基類(lèi) Enum 肩祥,該基類(lèi)為枚舉提供了一些通用方法。
- 枚舉的構(gòu)造函數(shù)
在 FruitEnum 中缩膝,我們并沒(méi)有定義構(gòu)造函數(shù)混狠,但在反編譯的代碼中,我們發(fā)現(xiàn)編譯器自動(dòng)幫我們添加了以下私有構(gòu)造函數(shù):
private FruitEnum(String s, int i) {
super(s, i);
}
該私有構(gòu)造函數(shù)只是簡(jiǎn)單調(diào)用了父類(lèi) Enum 的構(gòu)造函數(shù):
protected Enum(String name, int ordinal) {
this.name = name; // 枚舉常量的名稱(chēng)
this.ordinal = ordinal; // 枚舉常量在枚舉類(lèi)的位置疾层,從0開(kāi)始
}
實(shí)際上将饺,編譯器只允許私有的枚舉類(lèi)構(gòu)造函數(shù),并且顯示定義的枚舉構(gòu)造函數(shù)經(jīng)編譯器后云芦,都會(huì)被添加 name 和 ordinal 2個(gè)參數(shù)俯逾,用以調(diào)用父類(lèi) Enum 的構(gòu)造函數(shù)。
- 枚舉常量
再看反編譯后的 FruitEnum 中的成員變量:
public static final FruitEnum APPLE;
private static final FruitEnum $VALUES[];
其中 FruitEnum APPLE
對(duì)應(yīng)我們定義的枚舉常量 APPLE舅逸,而 FruitEnum $VALUES[]
則是容納所有枚舉常量的數(shù)組桌肴。
這兩個(gè)變量初始化操作如下:
static {
APPLE = new FruitEnum("APPLE", 0);
$VALUES = (new FruitEnum[] { APPLE });
}
可知,每個(gè)枚舉常量都是定義它的類(lèi)的對(duì)象琉历,各枚舉對(duì)象都是用 public static final
修飾的坠七,這也是枚舉對(duì)象被稱(chēng)為枚舉常量的原因水醋。
其次,FruitEnum APPLE
和 FruitEnum $VALUES[]
都是在靜態(tài)代碼塊中進(jìn)行初始化彪置,因此在 JVM 記載完成枚舉類(lèi)后拄踪,各枚舉常量都已被
創(chuàng)建完畢。由于枚舉類(lèi)中只允許出現(xiàn)私有構(gòu)造函數(shù)拳魁,固無(wú)法再實(shí)例化新的枚舉對(duì)象惶桐。
- 枚舉類(lèi)實(shí)例化
無(wú)法通過(guò)構(gòu)造函數(shù)這種一般手段實(shí)例化枚舉類(lèi),那么通過(guò)反射呢潘懊?
先嘗試以下形式:
Class<FruitEnum> fruitEnumClazz = FruitEnum.class;
FruitEnum fruitDemo1 = fruitEnumClazz.newInstance();
拋出了以下錯(cuò)誤:
因?yàn)?Class.newInstance 內(nèi)部是通過(guò)目標(biāo)Class的 無(wú)參構(gòu)造函數(shù) 創(chuàng)建實(shí)例的姚糊,而上文已經(jīng)說(shuō)了 FruitEnum 的實(shí)際構(gòu)造函數(shù)是:
private FruitEnum(String s, int i) {
super(s, i);
}
固會(huì)找不到對(duì)應(yīng)的構(gòu)造函數(shù),拋出異常授舟。
換種反射方式救恨,先獲取正確的構(gòu)造函數(shù):
// 獲取 FruitEnum 的構(gòu)造函數(shù)
Constructor<FruitEnum> fruitEnumConstructor = fruitEnumClazz.getDeclaredConstructor(String.class, int.class);
// 獲取訪問(wèn)權(quán)限
fruitEnumConstructor.setAccessible(true);
// 實(shí)例化
FruitEnum fruitDemo2 = fruitEnumConstructor.newInstance();
成功獲取了 FruitEnum 的構(gòu)造函數(shù),然而還是拋出了異常:
在 JVM 層面禁止了通過(guò)反射構(gòu)造枚舉實(shí)例的行為释树。
然而除了反射外肠槽,還可以通過(guò) Object.clone() 方法克隆一個(gè)已存在的對(duì)象。但是這種方式也是不行的奢啥,看 Enum 類(lèi)中的 clone()
方法:
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
Enum 類(lèi)重寫(xiě)了 Obejct 的 clone()
方法秸仙,只要嘗試通過(guò) clone()
方法構(gòu)造枚舉對(duì)象,都會(huì)拋出異常扫尺,并且該方法被設(shè)置為不能再重寫(xiě)筋栋。
- 單例
從編譯器、JVM正驻,再到 java 內(nèi)部設(shè)計(jì)弊攘,層層把關(guān),封死了實(shí)例化枚舉類(lèi)的這一企圖姑曙。因此有一種作法是通過(guò)枚舉的形式實(shí)現(xiàn)單例襟交,下面給個(gè)示例:
public class EnumSingleton{
private EnumSingleton() {
}
public static EnumSingleton getInstance(){
return Singleton.INSTANCE.getInstance();
}
private static enum Singleton {
INSTANCE;
private EnumSingleton singleton;
// JVM 保證了此方法絕對(duì)只調(diào)用一次
private Singleton(){
singleton = new EnumSingleton();
}
public EnumSingleton getInstance(){
return singleton;
}
}
}
-
線程安全
枚舉常量都是 static 類(lèi)型的,在枚舉類(lèi)加載完成后伤靠,會(huì)進(jìn)行枚舉常量的初始化捣域,之后枚舉類(lèi)無(wú)法再實(shí)例化和修改。java 的類(lèi)加載宴合、初始化過(guò)程是線程安全的焕梅,因此創(chuàng)建一個(gè) enum 是線程安全的。