枚举类型
关键字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页