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