Java 列舉類

NO IMAGE

手動實現列舉類

手動實現列舉類

例項有限而且固定的類,在Java裡被稱為列舉類。

早期採用通過定義類的方式來實現,可以採用如下設計方式

通過private將構造器隱藏起來

把這個類的所有可能例項都使用public static final 修飾的類變數來儲存

如果與必要,可以提供一些靜態方法,允許其他程式根據特定引數來獲取與之匹配的例項

使用列舉類可以使程式更加健壯,避免建立物件的隨意性

Java從JDK1.5後就增加了對列舉類的支援。

列舉類入門

Java5新增了一個enum關鍵字(它與class、interface關鍵字的地位相同),用以定義列舉類。列舉類是一種特殊的類,它一樣可以有自己的成員變數、方法,可以實現一個或多個介面,也可以定義自己的構造器。一個Java原始檔中最多隻能定義一個public訪問許可權的列舉類,且該Java原始檔也必須和該列舉類的類名相同。

列舉類可以實現一個或多個介面,使用enum定義的列舉類預設繼承了java.lang.Enum類,而不是預設繼承Object類,因此列舉類不能顯示繼承其他父類。其中java.lang.Enum類實現了java.lang.Serializable和java.lang.Comparable兩個介面。

使用enum定義、非抽象的列舉類預設會使用final修飾,因此列舉類不能派生子類。

列舉類的構造器只能使用private訪問控制符,如果省略了構造器的訪問控制符,則預設使用private修飾;如果強制指定訪問控制符,則只能指定private修飾符。

列舉類的所有例項必須在列舉類的第一行顯式列出,否則這個列舉類永遠都不能產生例項。列出這些例項時,系統會自動新增public static final 修飾,無須程式設計師顯式新增。

列舉類預設提供了一個values()方法,該方法可以很方便地遍歷所有的列舉值。

public enum SeasonEnum
{
//在第一行列出4個列舉例項
SPRING,SUMMER,FALL,WINTER;
}

編譯上面Java程式,將生成一個SeasonEnum.class檔案,這表明列舉類是一個特殊的Java類。
所有的列舉值之間以英文逗號(,)隔開,列舉值列舉結束後以英文分號作為結束。這些列舉值代表了該列舉類的所有可能的例項。

public class EnumTest 
{
public void judge(SeasonEnum s)
{
//switch語句裡的表示式可以是列舉值
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(SeasonEnum s : SeasonEnum.values())
{
System.out.println(s);
}
//使用列舉例項時,可通過EnumClass.variable形式來訪問
new EnumTest().judge(SeasonEnum.SPRING);
}
}

當switch控制表示式使用列舉型別時,後面case表示式中的值直接使用列舉值的名字,無須新增列舉類作為限定。
java.lang.Enum類中提供瞭如下幾個方法:

int compareTo(E o):該方法用於與指定列舉物件比較順序,同一個列舉例項只能與相同型別的列舉例項進行比較。如果該列舉物件位於指定列舉物件之後,則返回正整數;如果該列舉物件位於指定列舉物件之前,則返回負整數,否則返回零。

String name():返回此列舉例項的名稱,這個名稱就是定義列舉類時列出的所有列舉值之一。與此方法相比,大多數程式設計師應該優先考慮使用toString()方法,因為toString()方法返回更加使用者友好的名稱。

int ordinal():返回列舉值在列舉類中的索引值(就是列舉值在列舉宣告中的位置,第一個列舉值的索引值為零)。

String toString():返回列舉常量的名稱,與name方法相似,但toString()方法更常用。

public static <T extends Enum <T>> T valueOf(Class<T>enumType, String name):這是一個靜態方法,用於返回指定列舉類中指定名稱的列舉值。名稱必須與在該列舉類中宣告列舉值時所用的識別符號完全匹配,不允許使用額外的空白字元。

當程式使用System.out.println(s)語句來列印列舉值時,實際上輸出的是該列舉值的toString()方法,也就是輸出該列舉值的名字。

列舉類的成員變數、方法和構造器

列舉類的例項只能是列舉值,而不是隨意地通過new來建立列舉類物件。

public enum Gender 
{
MALE,FEMALE;
//定義一個public修飾的例項變數
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 GenderTest 
{
public static void main(String[] args) 
{
//通過Enum的valueOf()方法來獲取指定列舉類的列舉值
//Gender gender = Enum.valueOf(Gender.class, "FEMALE");
Gender g = Gender.valueOf("FEMALE");
g.setName("女");
System.out.println(g "代表:" g.getName());
g.setName("男");
System.out.println(g "代表:" g.getName());
}
}

列舉類通常應該設計成不可變類,成員變數值不允許改變,將列舉類的成員變數都使用private final修飾。如果將所有的成員變數都使用final修飾符來修飾,必須在構造器裡為這些成員變數指定初始值,為列舉類顯式定義帶引數的構造器。

一旦為列舉類顯式定義了帶引數的構造器,列出列舉值時就必須對應地傳入引數。

public enum Gender 
{
//此處的列舉值必須呼叫對應的構造器來建立
MALE("男"),FEMAL("女");
private final String name;
private Gender(String name)
{
this.name =name;
}
public String getName()
{
return this.name();
}
}

實現介面的列舉類

列舉類也可以實現一個或多個介面。與普通類實現一個或多個介面完全一樣,列舉類實現一個或多個介面時,也需要實現該介面所包含的方法。

public interface GenderDesc
{
void info();
}

如果由列舉類來實現介面裡的方法,則每個列舉值在呼叫該方法時都有相同的行為方式(因為方法體完全一樣)。如果需要每個列舉值在呼叫該方法時呈現出不同的行為方式,則可以讓每個列舉值分別來實現該方法,每個列舉值提供不同的實現方式,從而讓不同的列舉值呼叫該方法時具有不同的行為方式。

public enum Gender implements GenderDesc
{
// 此處的列舉值必須呼叫對應構造器來建立
MALE("男")
// 花括號部分實際上是一個類體部分
{
public void info()
{
System.out.println("這個列舉值代表男性");
}
},
FEMALE("女")
{
public void info()
{
System.out.println("這個列舉值代表女性");
}
};
// 其他部分與codes\06\6.9\best\Gender.java中的Gender類完全相同
private final String name;
// 列舉類的構造器只能使用private修飾
private Gender(String name)
{
this.name = name;
}
public String getName()
{
return this.name;
}
// 增加下面的info()方法,實現GenderDesc介面必須實現的方法
public void info()
{
System.out.println(
"這是一個用於用於定義性別的列舉類");
}
}

當建立MALE和FEMALE兩個列舉值時,後面又緊跟了一對花括號,這對花括號裡包含了一個info()方法定義。花括號部分實際上就是一個類體部分,在這種情況下,當建立MALE和FEMALE列舉值時,並不是直接建立Gender列舉類的例項,而是相當於建立Gender的匿名子類的例項。

並不是所有的列舉類都使用了final修飾。非抽象的列舉類才預設使用final修飾。對於一個抽象的列舉類而言——只要它包含了抽象方法,它就是抽象列舉類,系統會預設使用abstract修飾,而不是使用final修飾。

編譯上面的程式,生成了Gender.class、Gender$1.class和Gender$2.class三個檔案,證明了:MALE和FEMALE實際上是Gender匿名子類的例項,而不是Gender類的例項。當呼叫MALE和FEMALE兩個列舉值的方法時,就會看到兩個列舉值的方法表現不同的行為方式。

包含抽象方法的列舉類

public enum Operation 
{
PLUS
{
public double eval(double x, double y) 
{
return x   y;
}
},
MINUS
{
public double eval(double x, double y) 
{
return x - y;
}
},
TIMES
{
public double eval(double x, double y) 
{
return x * y;
}
},
DIVIDE
{
public double eval(double x, double y) 
{
return x / y;
}
};
//為列舉類定義一個抽象方法
//這個抽象方法由不同的列舉值提供不同的實現
public abstract double eval(double x, double y);
public static void main(String[] args) 
{
System.out.println(Operation.PLUS.eval(3, 4));
System.out.println(Operation.MINUS.eval(5, 4));
System.out.println(Operation.TIMES.eval(8, 8));
System.out.println(Operation.DIVIDE.eval(1, 5));
}
}

列舉類裡定義抽象方法時不能使用abstract關鍵字將列舉類定義成抽象類(因為系統自動會為它新增abstract關鍵字),但因為列舉類需要顯式建立列舉值,而不是作為父類,所以定義每個列舉值時必須為抽象方法提供實現,否則將出現編譯錯誤。