基礎知識
正如 Brett McLaughlin 在他的文章“枚舉類型入門”中所描述的那樣(推薦您先閱讀這篇文章),通過使用新的 enum 關鍵字創建指定的對象集合,您可以創建一個枚舉類型。然後,可以將每個指定的值看作是那個類的一個實例,這為您提供了指定的整數集合所無法提供的編譯時類型安全。清單 1 將創建一個枚舉類型,並將類型安全的枚舉值作為幫助器方法(helper method)的參數。該枚舉類型的 values() 方法返回這種類型的不同值的有序數組。
清單 1. 枚舉類型的例子
public class Loop {
enum Size {
Small,
Medium,
Large
}
public static void main(String args[]) {
for (Size s : Size.values()) {
helper(s);
}
}
private static void helper(Size s) {
System.out.println("Size value: " + s);
}
}
構造函數、方法和變量
在使用 enum 關鍵字創建新的枚舉類型時,實際上是在創建 java.lang.Enum 類的子類,其中,枚舉類型符合通用模式 Class Enum<E extends Enum<E>>,而 E 表示枚舉類型的名稱。枚舉類型的每一個值都將映射到 protected Enum(String name, int ordinal) 構造函數中,在這裡,每個值的名稱都被轉換成一個字符串,並且序數設置表示了每個設置的優先值。換句話說,enum Size {Small, Medium, Large} 將映射到清單 2 中所示的構造函數調用中:
清單 2. 映射的構造函數調用
new Enum<Size>("Small", 0);
new Enum<Size>("Medium", 1);
new Enum<Size>("Large", 2);
不必將構造函數的使用限制為間接 Enum 構造函數調用。在使用 enum 關鍵字時,將創建 Enum 的子類。您可以使用參數和任何別的東西為定義的每個名稱添加一些您自己的構造函數調用。名稱聲明可以看作是對構造函數的調用,您不必添加 new 關鍵字。這種方法允許您將數據作為參數值傳遞給構造函數調用,如清單 3 所示。該參數表示 Size 對象的枚舉集合的定價因子。位於枚舉類型定義之後的 main() 方法演示了這種用法。
清單 3. 定制構造函數的例子
public class Sample {
enum Size {
Small(0.8),
Medium(1.0),
Large(1.2);
double pricingFactor;
Size(double p) {
pricingFactor = p;
}
}
public static void main(String args[]) {
Size s = Size.Large;
double d = s.pricingFactor;
System.out.println(s + " Size has pricing factor of " + d);
}
}
運行該程序將返回給定 Size 的定價因子。您還可以定義一個類似於 getPricingFactor() 的方法,並將 pricingFactor 字段設置為 private,以便更多地將它作為類 JavaBean 的屬性對待。清單 4 給前面的例子添加了一個方法:
清單 4. 方法的例子
public class Sample2 {
enum Size {
Small(0.8),
Medium(1.0),
Large(1.2);
private double pricingFactor;
Size(double p) {
pricingFactor = p;
}
public double getPricingFactor() {
return pricingFactor;
}
}
public static void main(String args[]) {
Size s = Size.Large;
double d = s.getPricingFactor();
System.out.println(s + " Size has pricing factor of " + d);
}
}
對於這兩種情況,輸出均為:
Large Size has pricing factor of 1.2
預定義的方法
因為用戶定義的枚舉類型是 Enum 類型的子類,所以您需要繼承用於您的類型的那個類的所有方法。下面列出了完整的方法集合(E 表示枚舉類型自身):
public int compareTo(E e)
public boolean equals(Object o)
public final Class<E> getDeclaringClass()
public int hashCode()
public String name()
public int ordinal()
public String toString()
public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name)
一些方法看起來很熟悉,而其他一些方法則是特定於 Enum 類的。compareTo()、equals() 和 hashCode() 方法是典型的 Object 和 Comparable 方法,其中,compareTo() 報告聲明元素的順序。name() 和 ordinal() 方法返回構造函數參數,而 toString() 返回名稱。
getDeclaringClass() 和 valueOf() 方法需要稍多一些解釋。getDeclaringClass() 方法類似於 Object 的 getClass() 方法,但它沒必要返回相同的類。根據這個方法的 Javadoc 的說明:
對於具有特定於常量的類主體的 enum 常量,該方法返回的值可能不同於 Object.getClass() 方法返回的值。
接下來,我將解釋特定於常量的類主體。valueOf() 方法是靜態的,它允許您從類型的名稱中創建枚舉的值。
特定於常量的類主體
特定於常量的類主體是 enum 關鍵字的一個受支持的特性;不過,它們的使用應該受到嚴格的限制。這個概念正在深入到將枚舉類型的每個元素作為一個子類對待的領域。例如,在前面的例子中,Size 枚舉類型有一個定價因子參數和 getPricingFactor() 方法。但沒有構造函數參數,清單 5 展示了如何利用特定於常量的主體來做同樣的事。我們添加了一些額外的大小來讓這個例子更有趣些。在這裡,Small 的定價因子是 0.8,而 ExtraLarge 和 ExtraExtraLarge 的定價因子是 1.2。其余的大小則采用默認值,即 1.0。
清單 5. 特定於常量的主體
public class Sample3 {
enum Size {
Small {
public double getPricingFactor() {
return 0.8;
}
},
Medium,
Large,
ExtraLarge {
public double getPricingFactor() {
return 1.2;
}
},
ExtraExtraLarge {
public double getPricingFactor() {
return 1.2;
}
};
public double getPricingFactor() {
return 1.0;
}
}
public static void main(String args[]) {
for (Size s : Size.values()) {
double d = s.getPricingFactor();
System.out.println(s + " Size has pricing factor of " + d);
}
}
}
如果回頭想想前面描述過的 getDeclaringClass() 方法,您就能明白為什麼這些特定於常量的主體和 getClass() 能夠在擁有特定於常量的類主體的同時返回不同的類。
EnumMap 和 EnumSet
java.util 程序包中包含兩個類:EnumMap 和 EnumSet,這兩個類有助於使處理枚舉類型變得更容易一些。EnumMap 類提供了 java.util.Map 接口的一個特殊實現,該接口中的鍵(key)是一個枚舉類型。EnumSet 類提供了 java.util.Set 接口的實現,該接口保存了某種枚舉類型的值的集合。
清單 6 展示了 EnumMap 類的用法。在創建映射時,必須為枚舉的鍵傳入這個類。
清單 6. EnumMap 的例子
import java.util.*;
public class EnumMapSample {
enum Size {
Small,
Medium,
Large;
}
public static void main(String args[]) {
Map<Size, Double> map = new EnumMap<Size, Double>(Size.class);
map.put(Size.Small, 0.8);
map.put(Size.Medium, 1.0);
map.put(Size.Large, 1.2);
for (Map.Entry<Size, Double> entry : map.entrySet()) {
helper(entry);
}
}
private static void helper(Map.Entry<Size, Double> entry) {
System.out.println("Map entry: " + entry);
}
}
枚舉集合的作用類似於特性的集合,或者類似於某個枚舉類型的所有元素的值的子集。EnumSet 類擁有以下一系列的靜態方法,可以用這些方法從枚舉類型中獲取單個元素:
public static <E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType)
public static <E extends Enum<E>> EnumSet<E> complementOf(EnumSet<E> s)
public static <E extends Enum<E>> EnumSet<E> copyOf(Collection<E> c)
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType)
public static <E extends Enum<E>> EnumSet<E> of(E e)
public static <E extends Enum<E>> EnumSet<E> of(E first, E... rest)
public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2)
public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3)
public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4)
public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4, E e5)
public static <E extends Enum<E>> EnumSet<E> range(E from, E to)
一旦創建了 EnumSet,就可以像對待其他任何 Set 對象那樣對待這組對象。
結束語
使用枚舉類型的基本概念很簡單。您可以先定義一個指定的、封閉的值集合。然後,在需要這些值中的某一個值時,可以通過它的名稱來指定它。該名稱攜帶為其設置的類型。對於不同的大小,不是說 1 = Small, 2 = Medium, 3 = Large,並且確保沒有將 1 = Monday 的這類東西傳遞給期望獲得一個 Size 的方法,而是傳入 Small、Medium 或 Large 作為 Size,因為編譯器會確保您沒有傳入 Monday。這就是枚舉類型的簡單性。這些枚舉類型就是類本身,因此,可以對類進行的所有操作同樣可以作用於枚舉類型上。
此外,枚舉類型支持擁有構造函數、實例方法和變量,等等。應該對枚舉類型使用這些方面嗎?盡管使用這些方法和新的支持類肯定沒問題,但提供構造函數和覆蓋方法會有問題。說出為枚舉中的每個 Size 所支付的價格真的有意義嗎?或者說,在一個擁有枚舉類型 Size 變量的類中做這些更有意義嗎?
小心使用這些特性,不要因為這些特性可用就使用它們。要考慮到系統的總體設計,不要只圖快點完成工作。