一、枚舉簡介
某些情況下,類的對象有限且固定,比如季節,有春夏秋冬四個對象,行星類目前隻有8個對象,是以,像這些執行個體固定且有限的類稱為枚舉類。
在
JDK 1.5
之前沒有枚舉類型,那時候一般用接口常量來替代,如:
public static final int SEASON_SPRING=1;
public static final int SEASON_SUMMER=2;
public static final int SEASON_FALL=3;
public static final int SEASON_WINTER=4;
這種方式雖然定義簡單,但是類型不安全,季節實際上是一個int的整數,但是int整數之間可以加減,那麼進行
SEASON_SPRING+SEASON_SUMMER
,這樣的代碼完全正常,但是不合常理,還有列印輸出不明确,當列印季節
SEASON_SPRING
時,實際上列印了數字1。而使用
Java
枚舉的出現可以更恰當地表示該常量。
使用
enum
關鍵詞定義,與
class
、
interface
地位是一樣的,枚舉類也是一種特殊的類,它一樣可以有自己的成員變量、方法,可以實作一個或者多個接口,也可以定義自己的構造器。一個
Java
源檔案最多隻能定義一個
public
通路權限的枚舉類,且該
Java
源檔案也必須和該枚舉類的類名相同。
但是枚舉終究不是普通的類,與普通的類有着如下的簡單差別:
- 枚舉類可以實作一個或者多個接口,使用
定義的枚舉類預設繼承了enum
類,而不是預設繼承了java.lang.Enum
類,是以枚舉類不能顯式繼承其他父類,其中Object
類實作了java.lang.Enum
和java.lang.Serializable
兩個接口;java.lang.Comparable
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
}
既然枚舉類都繼承了
java.lang.Enum
類,是以枚舉類可以直接使用
java.lang.Enum
類中包含的方法,
java.lang.Enum
類有如下幾種方法 :
int compare(E o)
:該方法用于與指定對象比較順序,同一個枚舉執行個體隻能與相同類型的枚舉執行個體進行比較。如果該枚舉對象位于指定枚舉對象之後,則傳回正整數;如果該枚舉對象位于指定枚舉對象之前,則傳回負整數;否則傳回0;
String name()
:傳回此枚舉執行個體的名稱,這個名稱就是定義枚舉類時列出所有的枚舉值之一。與此方法相比,大多數程式員優先考慮
toString()
方法,因為
toString()
方法傳回更加使用者友好的名稱
int ordinal()
:傳回枚舉值在枚舉類中的索引值(就是枚舉值在枚舉聲明中的位置,第一個枚舉值得索引值為0);
String toString()
:傳回枚舉常量的名稱,與name方法相似,但是
toString()
方法更加常用;
public static<T extends Enum<T>>T valueOf(Class<T>enumType,String name)
:這是一個靜态方法,用于傳回指定枚舉類中指定名稱的枚舉值,名稱必須與該枚舉類中聲明枚舉值時所用的辨別符完全比對,不允許使用額外的空白字元。
當程式員使用
System.out.println(s)
語句來列印枚舉值時,實際上輸出的是該枚舉值的
toString()
方法,也就是輸出該枚舉值的名字。
- 使用
定義、非抽象的枚舉類預設會使用final修飾,是以枚舉類不能派生子類(對于抽象的枚舉類而言,系統會預設使用enum
修飾,而不是abstract
修飾);final
- 枚舉類的構造器隻能用
通路控制符,如果省略了構造器的通路控制符,預設為private
;如果強制指定通路控制符,則隻能指定private
修飾符;private
- 枚舉類的所有執行個體必須在枚舉類第一行顯式列出,否則這個枚舉類不能産生執行個體,列出這些執行個體時,系統自動加上
public static final
修飾,無需程式員顯式添加;
枚舉類提供了一個
方法,該方法可以很友善地周遊所有的枚舉值:value()
public enum Season {
//列出枚舉的4個執行個體
SPRING,SUMMER,FALL,WINTER;
}
public class TestEnum {
public void judge(Season s){
switch (s){
case SPRING:
System.out.println("春暖花開,正好踏春!");
break;
case SUMMER:
System.out.println("夏日炎炎,适合遊泳!");
break;
case FALL:
System.out.println("秋高氣爽,進補及時!");
break;
case WINTER:
System.out.println("冬日雪飄,圍爐賞雪!");
break;
}
}
public static void main(String[]args){
//枚舉類預設有一個values()方法,傳回該枚舉類的所有執行個體
for(Season s:Season.values()){
System.out.println(s);
}
//使用枚舉執行個體時,可以通過EnumClass.variable形式來通路
new TestEnum().judge(Season.SPRING);
}
}
運作結果:
SPRING
SUMMER
FALL
WINTER
春暖花開,正好踏春!
上述程式測試了
Season
枚舉類的用法,該類通過了
values()
方法傳回了
Season
枚舉類的所有執行個體,并通過循環疊代輸出了枚舉類的所有的執行個體
不僅如此,上面程式的switch表達式中還使用了
Season
對象作為表達式,這是
JDK1.5
增加枚舉後對于
switch
的擴充:
switch
的控制表達式可以是任何枚舉類型,當
switch
控制表達式使用枚舉類型時,後面的
case
表達式中的值直接使用枚舉值的名字,無需添加枚舉類作為限定。
二、枚舉的成員變量、方法和構造器
枚舉類也是一種類,知識它是比較特殊的類,是以它一樣可以定義成員變量、方法和構造器。下面以定義一個
People
枚舉類,該枚舉類裡包含了一個
name
執行個體變量。
public enum People {
MALE,FEMALE;
//定義一個String類型的成員變量
public String name;
}
public class TestPeople {
public static void main(String[]args){
People p=People.valueOf(People.class,"MALE");
//直接給枚舉的成員變量指派
p.name="女";
//通路枚舉的name執行個體變量
System.out.println(p+"表示:"+p.name);
}
}
運作結果:
MALE表示:女
并不能随意通過
new
來建立枚舉類的對象,
Java
應該把所有類設計成良好封裝的類,是以不應該直接通路
People
類的
name
成員變量,而是應該通過方法來控制對
name
的通路,否則可能很混亂的情形,比如将
p.name="男"
,就會表示
FEMALE
代表男的局面,是以可以用改進的
People
類設計
public enum People {
MALE,FEMALE;
//定義一個String類型的成員變量
private String name;
public void setName(String name){
switch(this){
case MALE:
if(name.equals("男")){
this.name=name;
}else{
System.out.println("參數錯誤!");
return;
}
break;
case FEMALE:
if(name.equals("女")){
this.name=name;
}else{
System.out.println("參數錯誤!");
return;
}
break;
}
}
public String getName(){
return this.name;
}
}
public class TestPeople {
public static void main(String[]args){
People p=People.valueOf(People.class,"MALE");
p.setName("女");
System.out.println(p+"表示:"+p.getName());
p.setName("男");
System.out.println(p+"表示:"+p.getName());
}
}
運作結果:
參數錯誤!
MALE表示:null
MALE表示:男
上述代碼通過
get
和
set
方法将
FEMALE
枚舉值得
name
變量設定為“男”,系統設定會提示錯誤資訊,實際上這種做法還是不夠好,枚舉類通常應該設計成不可變的類,也就是說成員變量值不應該允許改變,這樣會更加安全,代碼也會更加簡潔,是以建議将枚舉類的成員變量都使用
private final
修飾。
如果将所有的成員變量都使用了
final
修飾符來修飾,所有必須在構造器裡為這些成員變量指定初始值(或者在定義成員變量時指定預設值,或者在初始化塊中指定初始值,但是這兩種情況并不常見),是以應該為枚舉類顯式定義帶參數的構造器,一旦為枚舉類顯式定義了帶有參數的構造器,列出枚舉值得時候就必須對應地傳入參數。
public enum People {
//此處必須調用對應的枚舉構造器來建立
MALE("男"),FEMALE("女");
private final String name;
//枚舉類的構造器必須用private修飾
private People(String name){
this.name=name;
}
public String getName(){
return this.name;
}
}
從上面的程式中可以看出,當為
People
枚舉類建立一個
People(String name)
構造器之後,列出枚舉值就應該采用
MALE("男"),FEMALE("女");
來完成,也就是說,在枚舉類彙總列出枚舉值時,實際上就是調用了構造器建立枚舉類的對象,隻是這裡無須使用
new
關鍵詞,也無須顯式調用構造器,前面列出枚舉值時無須傳入參數,甚至無須使用括号,僅僅是因為前面的枚舉類包含無參數的構造器
上面一行代碼等同于:
public static final People MALE=new People("男");
public static final People FEMALE=new People("女");
三、實作接口的枚舉類
枚舉類也可以實作一個或者多個接口,與普通類實作一個或多個接口完全一樣,枚舉類實作一個或者多個接口時,也可以實作該接口所包含的方法,
interface GenderDesc{
void info();
}
public enum Gender implements GenderDesc {
MALE("男"),FEMALE("女");
//實作接口方法
public void info(){
System.out.println("這是一個用于定義性别的枚舉類");
}
private final String name;
private Gender(String name){
this.name = name;
}
public String getName(){
return this.name;
}
}
如果由枚舉類來實作接口的方法,則每個枚舉值在調用該方法時都會有同樣的行為方式(因為方法體完全一樣),如果需要每個枚舉值在調用該方法時呈現不同的行為方式,則可以讓每個枚舉值分别來實作該方法,每個枚舉值提供不同的實作方式,進而讓不同的枚舉值調用該方法時具有不同的行為方式,在下面的
Genden
枚舉類中,不同的枚舉值對于
info()
方法的實作各不相同
interface GenderDesc{
void info();
}
public enum Gender implements GenderDesc {
MALE("男"){
@Override
public void info() {
System.out.println("male info");
}
},
FEMALE("女"){
@Override
public void info() {
System.out.println("female info");
}
};
private final String name;
private Gender(String name){
this.name = name;
}
public String getName(){
return this.name;
}
}
上面這種方式,
{}
相當于建立
Gender
的匿名子類的執行個體,
MALE
和
FEMALE
實際上是
Gender
匿名子類的執行個體,而不是
Gender
類的執行個體,當調用了
MALE
和
FEMALE
兩個枚舉值的方法時,就會看到兩個枚舉值的方法表現不同的行為方式。
四、包含抽象方法的枚舉類
假設有一個
Operation
枚舉類,它的4個枚舉值
PLUS,MINUS,TIMES,DIVIDE
分别代表加減乘除4種運算,該枚舉類需要定義一個
eval()
方法來實運算。
綜上所述,可以考慮為
Operation
枚舉類定義一個
eval()
抽象方法,然後讓4個枚舉值分别為
eval()
提供不同的實作:
public enum Operation {
PLUS{
@Override
public int eval(int a, int b) {
return a + b;
}
},
MINUS{
@Override
public int eval(int a, int b) {
return a - b;
}
},
TIMES{
@Override
public int eval(int a,int b){
return a*b;
}
},
DIVIDE{
@Override
public int eval(int a,int b){
return a/b;
}
};
//定義一個抽象方法,
//每個枚舉值都提供不同的實作
public abstract int eval(int a, int b);
public static void main(String[] args){
System.out.println(Operation.PLUS.eval(10, 2));
System.out.println(Operation.MINUS.eval(10, 2));
System.out.println(Operation.TIMES.eval(10, 2));
System.out.println(Operation.DIVIDE.eval(10, 2));
}
}
運作結果:
12
8
20
5
它的4個匿名内部子類分别對應一個
class
檔案,枚舉類裡定義抽象方法時不能使用
abstract
關鍵詞将枚舉定義抽象類(因為系統自動會為它添加
abstract
關鍵詞),但因為枚舉類需要顯式建立枚舉值,而不是作為父類,是以定義每個枚舉值時必須為抽象方法提供實作,否則将出現編譯錯誤。
枚舉集合
java.util.EnumSet
和
java.util.EnumMap
是兩個枚舉集合。
EnumSet
保證集合中的元素不重複;
EnumMap
中的
key
是
enum
類型,而
value
則可以是任意類型。
//定義資料庫類型枚舉
public enum DataBaseType
{
MYSQUORACLE,DB2,SQLSERVER
}
//某類中定義的擷取資料庫URL的方法以及EnumMap的聲明
private EnumMap<DataBaseType,String>urls=new EnumMap<DataBaseType,String>(DataBaseType.class);
public DataBaseInfo()
{
urls.put(DataBaseType.DB2,"jdbc:db2://localhost:5000/sample");
urls.put(DataBaseType.MYSQL,"jdbc:mysql://localhost/mydb");
urls.put(DataBaseType.ORACLE,"jdbc:oracle:thin:@localhost:1521:sample");
urls.put(DataBaseType.SQLSERVER,"jdbc:microsoft:sqlserver://sql:1433;Database=mydb");
}
//根據不同的資料庫類型,傳回對應的URL
//@param type DataBaseType 枚舉類新執行個體
//@return
public String getURL(DataBaseType type)
{
return this.urls.get(type);
}
在實際使用中,
EnumMap
對象
urls
往往是由外部負責整個應用初始化的代碼來填充的。這裡為了示範友善,類自己做了内容填充。從本例中可以看出,使用
EnumMap
可以很友善地為枚舉類型在不同的環境中綁定到不同的值上。本例子中
getURL
綁定到
URL
上,在其他的代碼中可能又被綁定到資料庫驅動上去。
EnumSet
是枚舉類型的高性能
Set
實作,它要求放入它的枚舉常量必須屬于同一枚舉類型。
EnumSet
提供了許多工廠方法以便于初始化,如圖所示:
EnumSet
作為
Set
接口實作,它支援對包含的枚舉常量的周遊:
for(Operation op:EnumSet.range(Operation.PLUS,Operation.MULTIPLY)){
doSomeThing(op);
}