1.2 計(jì)數(shù):使用枚舉
Java5+
大多數(shù)應(yīng)用程序需要記錄一個(gè)值的有限集—— 即應(yīng)用程序中表示一組選擇或狀態(tài)的常量。一種常見(jiàn)的Java編程慣例是使用static int變量來(lái)表示這些值。然后讓程序通過(guò)比較這些值和其他變量的值來(lái)做出決定。盡管核心Java API本身也采用這種慣例,但它很可能導(dǎo)致嚴(yán)重的問(wèn)題!下面是一個(gè)有關(guān)水果信息的示例。該示例給出了使用int變量來(lái)表示枚舉數(shù)據(jù)時(shí)會(huì)出現(xiàn)的一些問(wèn)題。
public class FruitConstants {
// this is not such a good practice
public static final int APPLE = 1;
public static final int ORANGE = 2;
public static final int GRAPEFRUIT = 3;
public static final int BANANA = 4;
public static final int DURIAN = 5;
public static final int CITRUS = 6;
public static final int SWEET = 7;
public static final int SMELLY = 8;
public static final int UNKNOWN = 9;
public static int getCategory(int fruit) {
switch(fruit) {
case APPLE: case BANANA:
return SWEET;
case ORANGE: case GRAPEFRUIT:
return CITRUS;
case DURIAN:
return SMELLY;
}
return UNKNOWN;
}
}
|
其中,來(lái)自東南亞的水果榴蓮(Durian)具備“Sweet”和“Smelly”,但是這里的水果只能返回一個(gè)特點(diǎn),也就是說(shuō),所有的值只能是對(duì)應(yīng)單一的值而不是對(duì)應(yīng)多個(gè)值。另外一個(gè)主要的問(wèn)題是任何int值都可以傳遞給getCategory方法,無(wú)論它是否代表一個(gè)有效的水果。這可能導(dǎo)致細(xì)微的錯(cuò)誤,因?yàn)榫幾g器不關(guān)心是調(diào)用getCategory(SWEET)還是調(diào)用getCategory(42)。并且如果整型常量的值發(fā)生了變化,getCategory(3)顯示的將不再是正確的信息!
另一個(gè)問(wèn)題是使用水果和類(lèi)別的int值并無(wú)區(qū)別—— 它們都只是普通的int值。通過(guò)簡(jiǎn)單地將類(lèi)別常量置于一個(gè)不同的類(lèi)中可以部分地解決水果和類(lèi)別分離的問(wèn)題,但是它們?nèi)灾皇莍nt值而不是類(lèi)型安全(typesafe)的,也就是沒(méi)能將getCategory的參數(shù)限制到一個(gè)固定的值集合。
在Java 5中,有一個(gè)優(yōu)雅的解決方法:可以像在C語(yǔ)言中那樣創(chuàng)建枚舉類(lèi)型。這是一個(gè)新特性,它創(chuàng)建一個(gè)類(lèi),該類(lèi)包含一個(gè)所有它允許的實(shí)例清單。除了enum內(nèi)定義的實(shí)例外,不允許其他的實(shí)例。下面看一些enum的示例:
enum Fruit {APPLE, ORANGE, GRAPEFRUIT, BANANA, DURIAN}
enum FruitCategory {SWEET, CITRUS, SMELLY, UNKNOWN}
enum Dessert {PIE, CAKE, ICECREAM, BROWNIE}
|
以上每個(gè)例子均定義了一組不同的枚舉元素(選擇)。這樣做的好處是不會(huì)將Fruit值與其他類(lèi)型的相混合或相混淆。對(duì)待每個(gè)enum就好像它是一個(gè)不同的類(lèi)一樣。不能將FruitCategory作為一個(gè)參數(shù)傳遞給一個(gè)期望Dessert的值方法,也不能傳遞一個(gè)int值。下面擴(kuò)展Fruit enum以包含最初的FruitConstants類(lèi)具有的功能:
public enum Fruit {
APPLE, ORANGE, GRAPEFRUIT, BANANA, DURIAN;
public static FruitCategory getCategory(Fruit fruit) {
switch(fruit) {
case APPLE: case BANANA:
return FruitCategory.SWEET;
case ORANGE: case GRAPEFRUIT:
return FruitCategory.CITRUS;
case DURIAN:
return FruitCategory.SMELLY;
}
return FruitCategory.UNKNOWN;
}
}
|
可以看出一個(gè)enmu也可以像一個(gè)類(lèi)那樣定義方法。現(xiàn)在的getCategory方法采用Fruit作為參數(shù),并且只允許使用enum中定義的值。接下來(lái)這個(gè)代碼段將引起編譯錯(cuò)誤,而不會(huì)出現(xiàn)調(diào)用最初的未受保護(hù)的getCategory方法時(shí)出現(xiàn)的運(yùn)行時(shí)異常:
Fruit.getCategory(Dessert.PIE); // compile error
Fruit.getCategory(10); // compile error
|
如果每種水果管理它自己的類(lèi)別將會(huì)更好,因此為了完善Fruit類(lèi),將刪除getCategory的水果參數(shù)并使該方法為每個(gè)enum狀態(tài)返回不同的值。通過(guò)創(chuàng)建一個(gè)適用于所有值的抽象的getCategory方法并對(duì)每個(gè)enum以不同的方式重寫(xiě)它,可以完成此操作。它非常類(lèi)似于為每個(gè)枚舉值編寫(xiě)一個(gè)不同的子類(lèi),并讓每個(gè)這樣的子類(lèi)重寫(xiě)抽象的方法。
public enum Fruit {
APPLE
{ FruitCategory getCategory() {return FruitCategory.SWEET;} },
ORANGE
{ FruitCategory getCategory() {return FruitCategory.CITRUS;} },
GRAPEFRUIT
{ FruitCategory getCategory() {return FruitCategory.CITRUS;} },
BANANA
{ FruitCategory getCategory() {return FruitCategory.SWEET;} },
DURIAN
{ FruitCategory getCategory() {return FruitCategory.SMELLY;} };
abstract FruitCategory getCategory();
}
|
一旦創(chuàng)建一個(gè)類(lèi)似這樣的enum,就可以像對(duì)待其他對(duì)象那樣來(lái)對(duì)待APPLE值(使用靜態(tài)的Fruit.APPLE引用),并且可以調(diào)用它的getCategory方法來(lái)得到它的相應(yīng)類(lèi)別。現(xiàn)在可以為上面的類(lèi)添加一個(gè)main方法來(lái)說(shuō)明如何使用新的Fruit enum:
public static void main(String[] args) {
Fruit a = Fruit.APPLE;
// toString() returns "APPLE"
System.out.println ("The toString() for a: " + a);
// getCategory() returns "SWEET"
System.out.println ("a.getCategory() is: " + a.getCategory());
for (Fruit f : Fruit.values()) {
System.out.println ("Fruit is: " + f);
}
}
|
正如代碼中所示的那樣,可以使用values方法來(lái)迭代enum內(nèi)的所有值。enum的toString方法將會(huì)返回一個(gè)String,它具有和值相同的名字。使用enum而不是int來(lái)表示狀態(tài),可以使代碼具有更好的可讀性和更強(qiáng)的防錯(cuò)性。它明確地定義了一個(gè)特殊的枚舉狀態(tài)的所有值并可以防止有人使用不正確的值。