1.2 計數:使用枚舉
Java5+
大多數應用程序需要記錄一個值的有限集—— 即應用程序中表示一組選擇或狀態的常量。一種常見的Java編程慣例是使用static int變量來表示這些值。然后讓程序通過比較這些值和其他變量的值來做出決定。盡管核心Java API本身也采用這種慣例,但它很可能導致嚴重的問題!下面是一個有關水果信息的示例。該示例給出了使用int變量來表示枚舉數據時會出現的一些問題。
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;
}
}
|
其中,來自東南亞的水果榴蓮(Durian)具備“Sweet”和“Smelly”,但是這里的水果只能返回一個特點,也就是說,所有的值只能是對應單一的值而不是對應多個值。另外一個主要的問題是任何int值都可以傳遞給getCategory方法,無論它是否代表一個有效的水果。這可能導致細微的錯誤,因為編譯器不關心是調用getCategory(SWEET)還是調用getCategory(42)。并且如果整型常量的值發生了變化,getCategory(3)顯示的將不再是正確的信息!
另一個問題是使用水果和類別的int值并無區別—— 它們都只是普通的int值。通過簡單地將類別常量置于一個不同的類中可以部分地解決水果和類別分離的問題,但是它們仍只是int值而不是類型安全(typesafe)的,也就是沒能將getCategory的參數限制到一個固定的值集合。
在Java 5中,有一個優雅的解決方法:可以像在C語言中那樣創建枚舉類型。這是一個新特性,它創建一個類,該類包含一個所有它允許的實例清單。除了enum內定義的實例外,不允許其他的實例。下面看一些enum的示例:
enum Fruit {APPLE, ORANGE, GRAPEFRUIT, BANANA, DURIAN}
enum FruitCategory {SWEET, CITRUS, SMELLY, UNKNOWN}
enum Dessert {PIE, CAKE, ICECREAM, BROWNIE}
|
以上每個例子均定義了一組不同的枚舉元素(選擇)。這樣做的好處是不會將Fruit值與其他類型的相混合或相混淆。對待每個enum就好像它是一個不同的類一樣。不能將FruitCategory作為一個參數傳遞給一個期望Dessert的值方法,也不能傳遞一個int值。下面擴展Fruit enum以包含最初的FruitConstants類具有的功能:
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;
}
}
|
可以看出一個enmu也可以像一個類那樣定義方法。現在的getCategory方法采用Fruit作為參數,并且只允許使用enum中定義的值。接下來這個代碼段將引起編譯錯誤,而不會出現調用最初的未受保護的getCategory方法時出現的運行時異常:
Fruit.getCategory(Dessert.PIE); // compile error
Fruit.getCategory(10); // compile error
|
如果每種水果管理它自己的類別將會更好,因此為了完善Fruit類,將刪除getCategory的水果參數并使該方法為每個enum狀態返回不同的值。通過創建一個適用于所有值的抽象的getCategory方法并對每個enum以不同的方式重寫它,可以完成此操作。它非常類似于為每個枚舉值編寫一個不同的子類,并讓每個這樣的子類重寫抽象的方法。
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();
}
|
一旦創建一個類似這樣的enum,就可以像對待其他對象那樣來對待APPLE值(使用靜態的Fruit.APPLE引用),并且可以調用它的getCategory方法來得到它的相應類別。現在可以為上面的類添加一個main方法來說明如何使用新的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方法來迭代enum內的所有值。enum的toString方法將會返回一個String,它具有和值相同的名字。使用enum而不是int來表示狀態,可以使代碼具有更好的可讀性和更強的防錯性。它明確地定義了一個特殊的枚舉狀態的所有值并可以防止有人使用不正確的值。