關鍵字enum可以將一組具名的值的有限集合創建為一種新的類型,而這些具名的值可以作為常規的程序組件使用。這些具名的值稱為枚舉值,這種新的類型稱為枚舉類型。
下面是一個簡單的表示星期幾的枚舉:
1 public enum Day { 2 SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY 3 }
在創建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 @Override 23 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 @Override 21 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 }