基本使用
首先允悦,所有枚舉類型都有一個基類:java.lang.Enum抽象類衅疙,里面提供了一些基礎屬性和基礎方法莲趣。
枚舉類型不僅可以定義枚舉常量,還可以定義屬性饱溢、構造方法喧伞、普通方法、抽象方法等绩郎,比如潘鲫,我們定義了一個包含成員變量,構造方法以及抽象方法的四則運算操作符枚舉類型:
public enum Operator {
ADD("+") {
@Override
public int calculate(int a, int b) {
return a + b;
}
},
SUB("-") {
@Override
public int calculate(int a, int b) {
return a - b;
}
},
MUL("*") {
@Override
public int calculate(int a, int b) {
return a * b;
}
},
DIV("/") {
@Override
public int calculate(int a, int b) {
return a / b;
}
};
Operator (String operator) {
this.operator = operator;
}
private String operator;
public abstract int calculate(int a, int b);
public String getOperator() {
return operator;
}
}
Operator表示枚舉類型肋杖,其中的ADD溉仑、SUB等則是具體的枚舉值,可理解為Operator類型的四個實例對象状植。
這樣看起來浊竟,枚舉類型其實也很好理解,就是一個繼承了Enum類的普通類(根據(jù)后面解析津畸,這個枚舉類型實際上仍是個抽象類)振定,它將其所有的實例對象都寫在了類內部,類外部只能引用這些實例對象而不能重新創(chuàng)建新的實例對象肉拓。(根據(jù)后面解析后频,每個枚舉值都對應一個內部類,是這個內部類的實例對象暖途,而這個內部類繼承了枚舉類)
我們利用反編譯信息繼續(xù)挖掘其內部原理卑惜。
原理分析
使用javap Operator.class指令反編譯上面的枚舉類型Operator,得到的信息如下:
public abstract class Operator extends java.lang.Enum<Operator> {
public static final Operator ADD;
public static final Operator SUB;
public static final Operator MUL;
public static final Operator DIV;
public static Operator[] values();
public static Operator valueOf(java.lang.String);
public abstract int calculate(int, int);
public java.lang.String getOperator();
Operator(java.lang.String, int, java.lang.String, Operator$1);
static {};
}
從.class文件的反編譯信息中可以得出:
- 一個枚舉類型實際上是一個繼承了Enum類的抽象類丧肴,這也就解釋了為什么在類外部無法創(chuàng)建枚舉類型的新的實例對象残揉。
- 而具體的枚舉值則是Operator抽象類的靜態(tài)final實例,并且生成Operator.class時也生成了四個Operator$1~4.class文件芋浮,所以枚舉值實際上是繼承了Operator抽象類的內部類Operator1抱环、Operator2壳快、...的對象實例。
- 編譯出來的Operator類多出了兩個靜態(tài)方法:values和valueOf镇草,還生成了一個靜態(tài)代碼塊static{};
- 構造方法的參數(shù)變成了三個眶痰,多出了前面兩個參數(shù)。這兩個參數(shù)對應的是其父類Enum的構造參數(shù)的兩個屬性值:name和ordinal梯啤。
使用javap -c -v Operator.class查看更詳細的反匯編信息竖伯,其中重要的信息點:
-
InnerClasses字段,聲明了其中包含了四個內部類Operator$1~4因宇。
-
靜態(tài)代碼塊的具體執(zhí)行內容如下圖分析:
根據(jù)圖中注釋七婴,靜態(tài)代碼塊中分別創(chuàng)建了四個內部類的實例對象,并分別保存到成員變量ADD察滑,SUB打厘,MUL和DIV中,同時保存到一個新創(chuàng)建的長度為4類型為Operator的數(shù)組中贺辰,最終用編譯器生成的靜態(tài)字段$VALUES指向該數(shù)組户盯。
- 編譯器生成的values方法
該方法是一個公共靜態(tài)方法,可通過調用該方法返回這個枚舉類型的枚舉值數(shù)組饲化,即返回其中聲明的所有實例對象莽鸭。
該方法實際是調用clone方法來獲取$VALUES字段的值,并把類型強轉為Operatro[]類型返回吃靠。
實際上并不是真正意義上的clone硫眨,并沒有創(chuàng)建新的實例對象,仍是原有的在靜態(tài)代碼塊中創(chuàng)建的實例對象撩笆。
public static Operator[] values();
descriptor: ()[LOperator;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: getstatic #2 // Field $VALUES:[LOperator;
3: invokevirtual #3 // Method "[LOperator;".clone:()Ljava/lang/Object;
6: checkcast #4 // class "[LOperator;"
9: areturn
- 編譯器生成的valueOf方法
該方法也是一個公共靜態(tài)方法捺球,可通過調用該方法返回參數(shù)String對應的枚舉值。該方法會調用其父類Enum的valueOf方法夕冲,并把類型強轉為Operator。
public static Operator valueOf(java.lang.String);
descriptor: (Ljava/lang/String;)LOperator;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: ldc #5 // class Operator
2: aload_0
3: invokestatic #6 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast #5 // class Operator
9: areturn
- 構造方法增加了兩個參數(shù)
Enum類有一個帶有兩個參數(shù)的構造方法裂逐,name和ordinal是Enum類中為每個枚舉定義的兩個final屬性歹鱼,name表示我們定義的枚舉常量的名稱,ordinal則是枚舉常量對應的一個序號卜高,該序號從0開始弥姻,按照聲明順序賦給每個枚舉常量。
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
Enum類中還定義了clone掺涛、readObject等方法庭敦,其中clone方法為final類型,這兩個方法主要用于保證枚舉類型的不可變性阿趁,不能通過克隆聚请,序列化攻擊來復制枚舉,保證一個枚舉常量只有一個實例涡拘。
小結
枚舉類型本質上就是一個普通類疼电,經(jīng)編譯器處理后嚼锄,枚舉類型實際上是一個繼承了Enum類的抽象類,且編譯器為其添加了values和valueOf兩個方法蔽豺。
每一個枚舉常量實際上是一個繼承了枚舉抽象類的內部類的一個實例對象区丑,所有枚舉常量都是在靜態(tài)代碼塊中初始化,即在類加載期間就初始化修陡。