枚舉類
實例有限而且固定的類,在Java里被稱為枚舉類。
早期采用通過定義類的方式來實現(xiàn),可以采用如下設(shè)計方式
- 通過private將構(gòu)造器隱藏起來
- 把這個類的所有可能實例都使用public static final 修飾的類變量來保存
- 如果與必要,可以提供一些靜態(tài)方法,允許其他程序根據(jù)特定參數(shù)來獲取與之匹配的實例
- 使用枚舉類可以使程序更加健壯,避免創(chuàng)建對象的隨意性
Java從JDK1.5后就增加了對枚舉類的支持。
枚舉類入門
Java5新增了一個enum關(guān)鍵字(它與class、interface關(guān)鍵字的地位相同),用以定義枚舉類。枚舉類是一種特殊的類,它一樣可以有自己的成員變量、方法,可以實現(xiàn)一個或多個接口,也可以定義自己的構(gòu)造器。一個Java源文件中最多只能定義一個public訪問權(quán)限的枚舉類,且該Java源文件也必須和該枚舉類的類名相同。
- 枚舉類可以實現(xiàn)一個或多個接口,使用enum定義的枚舉類默認(rèn)繼承了java.lang.Enum類,而不是默認(rèn)繼承Object類,因此枚舉類不能顯示繼承其他父類。其中java.lang.Enum類實現(xiàn)了java.lang.Serializable和java.lang.Comparable兩個接口。
- 使用enum定義、非抽象的枚舉類默認(rèn)會使用final修飾,因此枚舉類不能派生子類。
枚舉類的構(gòu)造器只能使用private訪問控制符,如果省略了構(gòu)造器的訪問控制符,則默認(rèn)使用private修飾;如果強制指定訪問控制符,則只能指定private修飾符。 - 枚舉類的所有實例必須在枚舉類的第一行顯式列出,否則這個枚舉類永遠(yuǎn)都不能產(chǎn)生實例。列出這些實例時,系統(tǒng)會自動添加public static final 修飾,無須程序員顯式添加。
枚舉類默認(rèn)提供了一個values()方法,該方法可以很方便地遍歷所有的枚舉值。
public enum SeasonEnum
{
//在第一行列出4個枚舉實例
SPRING,SUMMER,FALL,WINTER;
}
編譯上面Java程序,將生成一個SeasonEnum.class文件,這表明枚舉類是一個特殊的Java類。
所有的枚舉值之間以英文逗號(,)隔開,枚舉值列舉結(jié)束后以英文分號作為結(jié)束。這些枚舉值代表了該枚舉類的所有可能的實例。
public class EnumTest
{
public void judge(SeasonEnum s)
{
//switch語句里的表達(dá)式可以是枚舉值
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)
{
//枚舉類默認(rèn)有一個values()方法,返回該枚舉類的所有實例
for(SeasonEnum s : SeasonEnum.values())
{
System.out.println(s);
}
//使用枚舉實例時,可通過EnumClass.variable形式來訪問
new EnumTest().judge(SeasonEnum.SPRING);
}
}
當(dāng)switch控制表達(dá)式使用枚舉類型時,后面case表達(dá)式中的值直接使用枚舉值的名字,無須添加枚舉類作為限定。
java.lang.Enum類中提供了如下幾個方法:
- int compareTo(E o):該方法用于與指定枚舉對象比較順序,同一個枚舉實例只能與相同類型的枚舉實例進(jìn)行比較。如果該枚舉對象位于指定枚舉對象之后,則返回正整數(shù);如果該枚舉對象位于指定枚舉對象之前,則返回負(fù)整數(shù),否則返回零。
- String name():返回此枚舉實例的名稱,這個名稱就是定義枚舉類時列出的所有枚舉值之一。與此方法相比,大多數(shù)程序員應(yīng)該優(yōu)先考慮使用toString()方法,因為toString()方法返回更加用戶友好的名稱。
int ordinal():返回枚舉值在枚舉類中的索引值(就是枚舉值在枚舉聲明中的位置,第一個枚舉值的索引值為零)。 - String toString():返回枚舉常量的名稱,與name方法相似,但toString()方法更常用。
- public static <T extends Enum <T>> T valueOf(Class<T>enumType, String name):這是一個靜態(tài)方法,用于返回指定枚舉類中指定名稱的枚舉值。名稱必須與在該枚舉類中聲明枚舉值時所用的標(biāo)識符完全匹配,不允許使用額外的空白字符。
當(dāng)程序使用System.out.println(s)語句來打印枚舉值時,實際上輸出的是該枚舉值的toString()方法,也就是輸出該枚舉值的名字。
枚舉類的成員變量、方法和構(gòu)造器
枚舉類的實例只能是枚舉值,而不是隨意地通過new來創(chuàng)建枚舉類對象。
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("參數(shù)錯誤");
return;
}
break;
case FEMALE:
if (name().equals("女"))
{
this.name = name;
}
else
{
System.out.println("參數(shù)錯誤");
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());
}
}
枚舉類通常應(yīng)該設(shè)計成不可變類,成員變量值不允許改變,將枚舉類的成員變量都使用private final修飾。如果將所有的成員變量都使用final修飾符來修飾,必須在構(gòu)造器里為這些成員變量指定初始值,為枚舉類顯式定義帶參數(shù)的構(gòu)造器。
一旦為枚舉類顯式定義了帶參數(shù)的構(gòu)造器,列出枚舉值時就必須對應(yīng)地傳入?yún)?shù)。
public enum Gender
{
//此處的枚舉值必須調(diào)用對應(yīng)的構(gòu)造器來創(chuàng)建
MALE("男"),FEMAL("女");
private final String name;
private Gender(String name)
{
this.name =name;
}
public String getName()
{
return this.name();
}
}
實現(xiàn)接口的枚舉類
枚舉類也可以實現(xiàn)一個或多個接口。與普通類實現(xiàn)一個或多個接口完全一樣,枚舉類實現(xiàn)一個或多個接口時,也需要實現(xiàn)該接口所包含的方法。
public interface GenderDesc
{
void info();
}
如果由枚舉類來實現(xiàn)接口里的方法,則每個枚舉值在調(diào)用該方法時都有相同的行為方式(因為方法體完全一樣)。如果需要每個枚舉值在調(diào)用該方法時呈現(xiàn)出不同的行為方式,則可以讓每個枚舉值分別來實現(xiàn)該方法,每個枚舉值提供不同的實現(xiàn)方式,從而讓不同的枚舉值調(diào)用該方法時具有不同的行為方式。
public enum Gender implements GenderDesc
{
// 此處的枚舉值必須調(diào)用對應(yīng)構(gòu)造器來創(chuàng)建
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;
// 枚舉類的構(gòu)造器只能使用private修飾
private Gender(String name)
{
this.name = name;
}
public String getName()
{
return this.name;
}
// 增加下面的info()方法,實現(xiàn)GenderDesc接口必須實現(xiàn)的方法
public void info()
{
System.out.println(
"這是一個用于用于定義性別的枚舉類");
}
}
當(dāng)創(chuàng)建MALE和FEMALE兩個枚舉值時,后面又緊跟了一對花括號,這對花括號里包含了一個info()方法定義。花括號部分實際上就是一個類體部分,在這種情況下,當(dāng)創(chuàng)建MALE和FEMALE枚舉值時,并不是直接創(chuàng)建Gender枚舉類的實例,而是相當(dāng)于創(chuàng)建Gender的匿名子類的實例。
并不是所有的枚舉類都使用了final修飾。非抽象的枚舉類才默認(rèn)使用final修飾。對于一個抽象的枚舉類而言——只要它包含了抽象方法,它就是抽象枚舉類,系統(tǒng)會默認(rèn)使用abstract修飾,而不是使用final修飾。
編譯上面的程序,生成了Gender.class、Gender$1.class和Gender$2.class三個文件,證明了:MALE和FEMALE實際上是Gender匿名子類的實例,而不是Gender類的實例。當(dāng)調(diào)用MALE和FEMALE兩個枚舉值的方法時,就會看到兩個枚舉值的方法表現(xiàn)不同的行為方式。
包含抽象方法的枚舉類
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;
}
};
//為枚舉類定義一個抽象方法
//這個抽象方法由不同的枚舉值提供不同的實現(xiàn)
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關(guān)鍵字將枚舉類定義成抽象類(因為系統(tǒng)自動會為它添加abstract關(guān)鍵字),但因為枚舉類需要顯式創(chuàng)建枚舉值,而不是作為父類,所以定義每個枚舉值時必須為抽象方法提供實現(xiàn),否則將出現(xiàn)編譯錯誤。