关键字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 }