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的职责链

  在职责链设计模式中,程序员以多种不同的方式来解决一个问题,然后将它们链接在一起。当一个请求到来时,它遍历这个链,直到链中的某个解决方案能够处理该请求。

src/com/my/chapter19/practice8.java · sumAll/Java编程思想 - 码云 - 开源中国 (gitee.com)

(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页

 

上一篇:c语言_Day3_06-28


下一篇:C基础——枚举enum