關鍵字enum可以將一組具名的值的有限集合建立為一種新的類型,而這些具名的值可以作為常規的程式組件使用。這些具名的值稱為枚舉值,這種新的類型稱為枚舉類型。
下面是一個簡單的表示星期幾的枚舉:
1 public enum Day {2 SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY3 }
在建立enum時,編譯器會自動添加一些有用的特性。比如建立toString()方法以便顯示某個enum執行個體的名字;建立ordinal()方法表示某個特定enum常量的申明順序;values()方法用來按照enum常量的申明順序產生這些常量構成的數組。enum看起來像是一種新的資料類型,除了編譯上面這些特殊的編譯行為,很大程度上可以將enum看成是一個普通的類來處理。這些內容在後面會有詳細的介紹。
public static void main(String[] args) { System.out.println(Day.class.getSuperclass()); for (Day day : Day.values()) { System.out.println(day.name() + " ordinal: " + day.ordinal()); }}
class java.lang.Enum
SUNDAY ordinal: 0
MONDAY ordinal: 1
TUESDAY ordinal: 2
WEDNESDAY ordinal: 3
THURSDAY ordinal: 4
FRIDAY ordinal: 5
SATURDAY ordinal: 6
上面的代碼中輸出了枚舉類型Day的父類及示範了values()、name()、ordinal()三個方法的調用。從輸出結果中可以看到Day的父類是java.lang.Enum,但是在定義Day的時候並沒有通過extends指明繼承java.lang.Enum,也不需要通過extends關鍵字指定。當建立enum時編譯器會產生一個相關的類,這個類會繼承java.lang.Enum。既然枚舉執行個體已經整合了java.lang.Enum,Java又不支援多繼承,所以enum不能再繼承其他的類,但是能實現介面。ordinal()方法返回一個int值,這是每個enum執行個體在申明時的次序,從0開始。
除了不能繼承自一個enum外,基本上可以將enum看做一個常規的類。也就是說可以向enum中添加方法,比如返回enum自身描述的方法,還可以添加main方法。下面是一個示範enum添加自訂方法和實現介面的例子。
1 public enum Signal implements ObjectDescription { 2 Red("紅燈", "敢過去就是6分,還要罰款哦"), 3 Yellow("黃燈", "黃燈你也不敢過"), 4 Green("綠燈", "綠燈也得小心過啊"); 5 6 private String name; 7 private String description; 8 9 private Signal(String name, String description) {10 this.name = name;11 this.description = description;12 }13 14 private String getName() {15 return name;16 }17 18 private String getDescription() {19 return description;20 }21 22 @Override23 public String todo() {24 return "Signal類用於表示交通訊號指示燈," + this + "用於表示" + this.getName();25 }26 27 public static void main(String[] args) {28 for (Signal signal : Signal.values()) {29 System.out.println(signal.todo());30 }31 for (Signal signal : Signal.values()) {32 System.out.println(signal.getName() + ": "33 + signal.getDescription());34 }35 }36 37 }
注意:如果要自訂方法,那麼必須在enum執行個體序列的最後添加一個分號。同時,Java要求必須先定義enum執行個體,否則編譯時間會報錯。
enum的構造器無論是不是private,都只能在enum定義的內部使用來建立enum執行個體,一旦enum的定義結束,編譯器就不允許再使用其構造器來建立任何執行個體了。
使用介面組織枚舉
無法使用繼承限制了枚舉的使用,比如需要用enum表示食物,但同時必須分為水果,點心等類型的時候就沒那麼方便的滿足了。
下面通過在一個介面內部建立實現該介面的枚舉,從而達到對元素進行分類組織的目的。
1 public interface Food { 2 enum Appetizer implements Food { 3 SALAD, SOUP, SPRING_ROLLS; 4 } 5 6 enum MainCourse implements Food { 7 LASAGNE, BURRITO, PAD_THAI, LENTILS, HUMMOUS, VINDALOO; 8 } 9 10 enum Dessert implements Food {11 TIRAMISU, GELATO, BLACK_fOREST_CAKE, FRUIT;12 }13 14 enum Coffee implements Food {15 BLACK_COFFEE, DECAF_COFFEE, LATTE;16 }17 }
對於enum而言,實現介面是使其子類化的唯一辦法。通過上面的形式,成功的對不同的食物進行分組,但都是Food。
枚舉的枚舉
下面是一個枚舉的隨機播放器,是一個工具類。
1 public class Enums { 2 private static Random rand = new Random(47); 3 4 public static <T extends Enum<T>> T randrom(Class<T> ec) { 5 return random(ec.getEnumConstants()); 6 } 7 8 public static <T> T random(T[] values) { 9 return values[rand.nextInt(values.length)];10 }11 }
結合工具類及上面Food介面的內容,下面使用枚舉的枚舉實現一個產生隨機菜單的例子。
1 public enum Course { 2 APPETIZER(Food.Appetizer.class), MAINCOURSE(Food.MainCourse.class), DESSERT( 3 Food.Dessert.class), COFFEE(Food.Coffee.class); 4 private Food[] values; 5 6 private Course(Class<? extends Food> kind) { 7 // 返回枚舉中所有的元素,及所有執行個體構成的數組,如果kind不是枚舉返回null 8 values = kind.getEnumConstants(); 9 }10 11 public Food randomSelection() {12 return Enums.random(values);13 }14 15 public static void main(String[] args) {16 // 產生5份隨機菜單17 for (int i = 0; i < 5; i++) {18 for (Course c : Course.values()) {19 Food food = c.randomSelection();20 System.out.println(food);21 }22 System.out.println("--------------------");23 }24 }25 }
下面給出一個驗證values()方法不是通過父類繼承的方法。
1 public enum Signal implements ObjectDescription { 2 Red("紅燈", "敢過去就是6分,還要罰款哦"), Yellow("黃燈", "黃燈你也不敢過"), Green("綠燈", "綠燈也得小心過啊"); 3 4 private String name; 5 private String description; 6 7 private Signal(String name, String description) { 8 this.name = name; 9 this.description = description;10 }11 12 private String getName() {13 return name;14 }15 16 private String getDescription() {17 return description;18 }19 20 @Override21 public String todo() {22 return "Signal類用於表示交通訊號指示燈," + this + "用於表示" + this.getName();23 }24 25 public static void main(String[] args) {26 Set<Method> methodSet = new HashSet<Method>();27 Method[] signalMethods = Signal.class.getMethods();28 for (Method m : signalMethods) {29 methodSet.add(m);30 }31 Method[] superClassMethods = Signal.class.getSuperclass().getMethods();32 for (Method m : superClassMethods) {33 methodSet.remove(m);34 }35 System.out.println(methodSet);36 }37 38 }