Java 枚举类型
关键字enum可以将一组具名的值有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用。
1、基本enum特性
①values()方法返回enum实例的数组,可以遍历enum实例
②ordinal()方法返回一个int值,这是每个enum实例在声明时的次序,从0开始
③getDeclaringClass()方法能够知道其所属的enum类
④name()方法返回enum实例声明时的名字
⑤valueOf()根据给定的名字返回相应的enum实例
⑥Enum类实现了Comparable接口,具有compareTo()方法
⑦Enum类实现了Serializable接口
⑧通过static import可以静态导入其他包中的enum实例,无需再用enum类型来修饰enum实例
2、向enum中添加新方法
在添加新方法时,enum实例序列的最后添加一个分号,并且必须先定义实例。
1 public enum OzWitch {
2 WEST("This is west"),
3 NORTH("This is north"),
4 EAST("This is east"),
5 SOUTH("This is south");
6
7 private String description;
8 private OzWitch(String description){ this.description = description; }
9 public String getDescription(){ return description; }
10
11 @Override
12 public String toString() {
13 String id = name();
14 return id + ":" + getDescription();
15 }
16
17 public static void main(String[] args) {
18 for(OzWitch witch: OzWitch.values())
19 System.out.println(witch);
20 }
21 }
22
23 /*Output:
24 WEST:This is west
25 NORTH:This is north
26 EAST:This is east
27 SOUTH:This is south
28 */
3、values()方法
Explore[compareTo, equals, getClass, getDeclaringClass, hashCode, name, notify, notifyAll, ordinal, toString, valueOf, values, wait]
Enum[compareTo, equals, getClass, getDeclaringClass, hashCode, name, notify, notifyAll, ordinal, toString, valueOf, wait]
对比可知在Enum类中并没有values()方法。通过反编译,可以知道values()方法是由编译器添加的static方法。同时也添加了valueOf()方法,该方法只有一个参数,而在Enum类中的valueOf()方法需要两个参数。
将enum向上转型为Enum就无法访问values()方法,但是在Class中有一个getEnumConstants()方法,通过该方法仍然可以取得所有enum实例。
4、实现接口
所有的enum都继承自java.lang.Enum类。由于Java不支持多重继承,所以enum不能再继承其他,但是可以实现一个或多个接口。
5、随机选取
<T extends Enum<T>>表示T是enum实例。
1 public class Enums {
2 private static Random rand = new Random(47L);
3
4 public Enums() {
5 }
6
7 public static <T extends Enum<T>> T random(Class<T> ec) {
8 return (Enum)random((Object[])((Enum[])ec.getEnumConstants()));
9 }
10
11 public static <T> T random(T[] values) {
12 return values[rand.nextInt(values.length)];
13 }
14 }
6、使用接口组织枚举
在一个接口内部,创建实现该接口的枚举,以此将元素进行分组。这可以达到将枚举元素分类组织的目的。
当需要与一大堆类型打交道时,接口就不如enum好用了。为了创建一个“枚举的枚举”,可以创建一个新的enum,然后用其实例包装原接口中的每一个enum类。
1 enum Meal {
2 APPETIZER(Food.Appetizer.class),
3 COFFEE(Food.Coffee.class);
4 private Food[] values;
5 private Meal(Class<? extends Food> kind) {
6 values = kind.getEnumConstants();
7 }
8 public interface Food {
9 enum Appetizer implements Food {
10 SALAD, SOUP, SPRING_ROLLS;
11 }
12 enum Coffee implements Food {
13 BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,
14 LATTE, CAPPUCCINO, TEA, HERB_TEA;
15 }
16 }
17 public Food randomSelection() {
18 return Enums.random(values);
19 }
20 }
7、EnumSet
EnumSet用于替代传统的基于int的“位标志”,这种标志可以用来表示某种“开/关”信息。对同一个参数重复地调用add()方法会被忽略掉。
1 enum AlarmPoints{START1,START2,LOBBY,OFFICE1,OFFICE2,OFFICE3,BATHROOM}
2
3 public class EnumSets {
4 public static void main(String[] args) {
5 EnumSet<AlarmPoints> points = EnumSet.noneOf(AlarmPoints.class);//Empty set
6 points.add(AlarmPoints.BATHROOM);
7 System.out.println(points);
8 points.addAll(EnumSet.of(AlarmPoints.OFFICE1,AlarmPoints.OFFICE2,AlarmPoints.OFFICE3));
9 System.out.println(points);
10 points = EnumSet.allOf(AlarmPoints.class);
11 points.removeAll(EnumSet.of(AlarmPoints.START1,AlarmPoints.START2));
12 System.out.println(points);
13 points.removeAll(EnumSet.range(AlarmPoints.OFFICE1,AlarmPoints.OFFICE3));
14 System.out.println(points);
15 points = EnumSet.complementOf(points);//取差集
16 System.out.println(points);
17 }
18 }
19
20 /*Output:
21 [BATHROOM]
22 [OFFICE1, OFFICE2, OFFICE3, BATHROOM]
23 [LOBBY, OFFICE1, OFFICE2, OFFICE3, BATHROOM]
24 [LOBBY, BATHROOM]
25 [START1, START2, OFFICE1, OFFICE2, OFFICE3]
26 */
8、EnumMap
EnumMap是一种特殊的Map,它要求其中的键(key)必须来自一个enum。由于enum本身的限制,所以enumMap在内部可由数组实现。
与EnumSet一样,enum实例定义时的次序决定了其在EnumMap中的顺序。enum的每个实例作为一个键,总是存在的。如果没有用put()方法来存入相应值的话,其对应的值就是null。
1 enum AlarmPoints{START1,START2,LOBBY,OFFICE1,OFFICE2,OFFICE3,BATHROOM}
2
3 public class EnumMaps {
4 public static void main(String[] args) {
5 EnumMap<AlarmPoints,Command> em =
6 new EnumMap<AlarmPoints, Command>(AlarmPoints.class);
7 em.put(AlarmPoints.OFFICE1, new Command() {
8 @Override
9 public void action() {
10 System.out.println("This is office1");
11 }
12 });
13 em.put(AlarmPoints.OFFICE2, new Command() {
14 @Override
15 public void action() {
16 System.out.println("This is office2");
17 }
18 });
19 for(Map.Entry<AlarmPoints,Command> e: em.entrySet()){
20 System.out.print(e.getKey() + ":");
21 e.getValue().action();
22 }
23 }
24 }
25
26 interface Command{ void action(); }
27
28 /*Output:
29 OFFICE1:This is office1
30 OFFICE2:This is office2
31 */
9、常量相关的方法
(1)abstract方法
要实现常量相关的方法,需要为enum定义一个或多个abstract方法,然后为每个enum实例实现该抽象方法。还可以覆盖常量相关的方法。
1 enum ConstantSpecificMethod{
2 DATE_TIME{
3 String getInfo() { return "Time"; }
4 String f(){ return "Overridden method"; }
5 },
6 CLASSPATH{
7 String getInfo() { return "classpath"; }
8 };
9
10 abstract String getInfo();
11 String f(){ return "default behavior"; }
12
13 public static void main(String[] args) {
14 for(ConstantSpecificMethod csm: values())
15 System.out.println(csm + ":" + csm.getInfo() + "," + csm.f());
16 }
17 }
18
19 /*Output:
20 DATE_TIME:Time,Overridden method
21 CLASSPATH:classpath,default behavior
22 */
(2)使用enum的职责链
在职责链设计模式中,程序员以多种不同的方式来解决一个问题,然后将它们链接在一起。当一个请求到来时,它遍历这个链,直到链中的某个解决方案能够处理该请求。
(3)使用enum的状态机
枚举类型非常适合用来创建状态机。一个状态机可以具有有限个特定的状态,它通常根据输入,从一个状态转移到下一个状态,不过也可能存在瞬时状态,而一旦任务执行结束,状态机就会立刻离开瞬时状态。
10、多路分发
如果要执行的操作包含了不止一个类型未知的对象时,需要通过多路分发来解决。要利用多路分发,就必须为每一个类型提供一个实际的方法调用。
(1)使用enum分发
1 enum Outcome{ WIN,LOSE,DRAW }
2
3 interface Competitor<T extends Competitor<T>>{
4 Outcome compete(T competitor);
5 }
6
7 class RoShamBo{
8 public static <T extends Competitor<T>>
9 void match(T a,T b){
10 System.out.println(a + " vs " + b + " :" + a.compete(b));
11 }
12
13 public static <T extends Enum<T> & Competitor<T>>
14 void play(Class<T> rsbClass,int size){
15 for(int i=0;i<size;i++){
16 match(Enums.random(rsbClass),Enums.random(rsbClass));
17 }
18 }
19 }
20
21 public enum RoShamBo2 implements Competitor<RoShamBo2>{
22 PAPER(Outcome.DRAW,Outcome.LOSE,Outcome.WIN),
23 SCISSORS(Outcome.WIN,Outcome.DRAW,Outcome.LOSE),
24 ROCK(Outcome.LOSE,Outcome.WIN,Outcome.DRAW);
25
26 private Outcome vPAPER,vSCISSORS,vROCK;
27 RoShamBo2(Outcome paper,Outcome scissors,Outcome rock){
28 this.vPAPER = paper;
29 this.vSCISSORS = scissors;
30 this.vROCK = rock;
31 }
32
33 @Override
34 public Outcome compete(RoShamBo2 it) {
35 switch (it){
36 default:
37 case PAPER: return vPAPER;
38 case SCISSORS: return vSCISSORS;
39 case ROCK: return vROCK;
40 }
41 }
42
43 public static void main(String[] args) {
44 RoShamBo.play(RoShamBo2.class,6);
45 }
46 }
47
48 /*Output:
49 ROCK vs ROCK :DRAW
50 SCISSORS vs ROCK :LOSE
51 SCISSORS vs ROCK :LOSE
52 SCISSORS vs ROCK :LOSE
53 PAPER vs SCISSORS :LOSE
54 PAPER vs PAPER :DRAW
55 */
(2)使用常量相关的方法
常量相关的方法允许我们为每个enum实例提供方法的不同实现,这可以解决多路分发。
1 public enum RoShamBo3 implements Competitor<RoShamBo3>{
2 ROCK{
3 @Override
4 public Outcome compete(RoShamBo3 opponent) {
5 return compete(SCISSORS,opponent);
6 }
7 },
8 SCISSORS{
9 @Override
10 public Outcome compete(RoShamBo3 opponent) {
11 return compete(PAPER,opponent);
12 }
13 },
14 PAPER{
15 @Override
16 public Outcome compete(RoShamBo3 opponent) {
17 return compete(ROCK,opponent);
18 }
19 };
20
21 Outcome compete(RoShamBo3 loser,RoShamBo3 oppoent){
22 return ((oppoent == this) ? Outcome.DRAW :
23 ((oppoent == loser) ? Outcome.WIN : Outcome.LOSE));
24 }
25
26 public static void main(String[] args) {
27 RoShamBo.play(RoShamBo3.class,6);
28 }
29 }
30
31 /*Output:
32 PAPER vs PAPER :DRAW
33 SCISSORS vs PAPER :WIN
34 SCISSORS vs PAPER :WIN
35 SCISSORS vs PAPER :WIN
36 ROCK vs SCISSORS :WIN
37 ROCK vs ROCK :DRAW
38 */
(3)使用EnumMap分发
使用EnumMap能够实现“真正的”两路分发。
1 public enum RoShamBo4 implements Competitor<RoShamBo4>{
2 PAPER,SCISSORS,ROCK;
3 static EnumMap<RoShamBo4,EnumMap<RoShamBo4,Outcome>>
4 table = new EnumMap<RoShamBo4, EnumMap<RoShamBo4, Outcome>>(RoShamBo4.class);
5 static {
6 for(RoShamBo4 it : RoShamBo4.values()){
7 table.put(it,new EnumMap<RoShamBo4, Outcome>(RoShamBo4.class));
8 }
9 initRow(PAPER,Outcome.DRAW,Outcome.LOSE,Outcome.WIN);
10 initRow(SCISSORS,Outcome.WIN,Outcome.DRAW,Outcome.LOSE);
11 initRow(ROCK,Outcome.LOSE,Outcome.WIN,Outcome.DRAW);
12 }
13 static void initRow(RoShamBo4 it,Outcome vPAPER,Outcome vSCISSORS,Outcome vROCK){
14 EnumMap<RoShamBo4,Outcome> row = RoShamBo4.table.get(it);
15 row.put(RoShamBo4.PAPER,vPAPER);
16 row.put(RoShamBo4.SCISSORS,vSCISSORS);
17 row.put(RoShamBo4.ROCK,vROCK);
18 }
19 public Outcome compete(RoShamBo4 it){
20 return table.get(this).get(it);
21 }
22
23 public static void main(String[] args) {
24 RoShamBo.play(RoShamBo4.class,6);
25 }
26 }
27
28 /*Output:
29 ROCK vs ROCK :DRAW
30 SCISSORS vs ROCK :LOSE
31 SCISSORS vs ROCK :LOSE
32 SCISSORS vs ROCK :LOSE
33 PAPER vs SCISSORS :LOSE
34 PAPER vs PAPER :DRAW
35 */
(4)使用二维数组
每个enum实例都有一个固定的值(基于其声明的次序),并且可以通过ordinal()方法取得该值。因此我们可以使用二维数组,将竞争者映射到竞争结果。
1 public enum RoShamBo5 implements Competitor<RoShamBo5>{
2 PAPER,SCISSORS,ROCK;
3 private static Outcome[][] table = {
4 {Outcome.DRAW,Outcome.LOSE,Outcome.WIN},//PAPER
5 {Outcome.WIN,Outcome.DRAW,Outcome.LOSE},//SCISSORS
6 {Outcome.LOSE,Outcome.WIN,Outcome.DRAW},//ROCK
7 };
8 public Outcome compete(RoShamBo5 other){
9 return table[this.ordinal()][other.ordinal()];
10 }
11
12 public static void main(String[] args) {
13 RoShamBo.play(RoShamBo5.class,6);
14 }
15 }
16
17 /*Output:
18 ROCK vs ROCK :DRAW
19 SCISSORS vs ROCK :LOSE
20 SCISSORS vs ROCK :LOSE
21 SCISSORS vs ROCK :LOSE
22 PAPER vs SCISSORS :LOSE
23 PAPER vs PAPER :DRAW
24 */
参考于《Java编程思想》,第590~619页