枚举类型
枚举类型的定义¶
[[枚举类型的使用]]
enum GENDER {
MALE, FEMALE, NOTTELLING
}
public static void main(String [] args){
GENDER gender = GENDER.MALE; // 每个枚举值都是是枚举类型的对象示例
System.out.println(gender.toString() + " " + gender.ordinal()) //编译器自动生成
}
toString()
方法,用于打印实例名称(或使用name())
- 使用类名.values()
获取该枚举类所有对象的数组
- 枚举类型都隐式地继承自java.lang.Enum
类,因此枚举类不能再去继承其他类,但是可以去实现接口
- 可以直接向上转型为Enum
- Enum向下转型e.getClass().getEnumContants()
没有枚举的类型调用会返回null
- 枚举类可以添加自定义方法,枚举类也可以持有数据(并且定义构造函数)
public enum OzWitch { WEST("Miss"), NORTH("Glinda"), EAST("Wicked"), SOUTH("Good"); private String description; private OzWitch(String description) { this.description = description; } public String getDescription() { return description; } }
- 要注意的是这个构造函数是private的,仅限于内部初始化预定的枚举实例,不能在外部使用new创建任意对象
public enum ConstantSpecificMethod {
DATE_TIME {
@Override String getInfo() {
return
DateFormat.getDateInstance()
.format(new Date());
}
},
CLASSPATH {
@Override String getInfo() {
return System.getenv("CLASSPATH");
}
},
VERSION {
@Override String getInfo() {
return System.getProperty("java.version");
}
};
abstract String getInfo();
public static void main(String [] args) {
for(ConstantSpecificMethod csm : values())
System.out.println(csm.getInfo());
}
}
- 分组枚举
-
使用接口实现,外层接口没有定义任何方法,因此任何一个类都可以实现这个接口,每个枚举类都实现这个接口是为了按照一个类型来使用
public interface Food { enum Appetizer implements Food { SALAD, SOUP, SPRING_ROLLS; } enum MainCourse implements Food { LASAGNE, BURRITO, PAD_THAI, LENTILS, HUMMUS, VINDALOO; } enum Dessert implements Food { TIRAMISU, GELATO, BLACK_FOREST_CAKE, FRUIT, CREME_CARAMEL; } enum Coffee implements Food { BLACK_COFFEE, DECAF_COFFEE, ESPRESSO, LATTE, CAPPUCCINO, TEA, HERB_TEA; } } package enums.menu; import static enums.menu.Food.*; public class TypeOfFood { public static void main(String [] args) { Food food = Appetizer.SALAD; food = MainCourse.LASAGNE; food = Dessert.GELATO; food = Coffee.CAPPUCCINO; } }
-
嵌套的枚举
public enum Course { APPETIZER(Food.Appetizer.class), MAINCOURSE(Food.MainCourse.class), DESSERT(Food.Dessert.class), COFFEE(Food.Coffee.class); private Food [] values; private Course(Class <? extends Food> kind) { values = kind.getEnumConstants(); } public Food randomSelection() { return Enums.random(values); } }
枚举类型的集合¶
- EnumSet
- 用于使用枚举类型标识状态,需要从枚举类型创建
//创建空枚举 EnumSet <AlarmPoints> points = EnumSet.noneOf(AlarmPoints.class); points.add(BATHROOM); //从另一个 EnumSet 加入 points.addAll(EnumSet.of(STAIR1, STAIR2, KITCHEN)); //从 Enum 加入 points = EnumSet.allOf(AlarmPoints.class); //从 EnumSet 删除 points.removeAll(EnumSet.of(STAIR1, STAIR2, KITCHEN)); points.removeAll(EnumSet.range(OFFICE1, OFFICE4)); points = EnumSet.complementOf(points);
- 通过long存储标志实现,支持多余64个枚举类型(会自动给引入新的long)
-
存储顺序由在Enum中的声明顺序决定
-
EnumMap
- 所有的键来源某个枚举类型(实际上就是一个数组)除此之外与普通map一样
- 所有的枚举类型会被作为键初始化加入到map,但是对应的val为null
枚举类型的高级应用职责链¶
职责链¶
- 把解决问题的方法串成链,当请求到达时会顺着链传递下去知道遇到某个可以处理请求的方法(事件冒泡)
//一个邮局,针对不同类型的邮件有一系列处理方式,根据优先级高低一次对邮件进行处理直至成功 public class PostOffice { enum MailHandler { GENERAL_DELIVERY { @Override boolean handle(Mail m) { switch(m.generalDelivery) { case YES: System.out.println( "Using general delivery for " + m); return true; default: return false; } } }, MACHINE_SCAN { @Override boolean handle(Mail m) { switch(m.scannability) { case UNSCANNABLE: return false; default: switch(m.address) { case INCORRECT: return false; default: System.out.println( "Delivering "+ m + " automatically"); return true; } } } },... }; //每个手段的处理方式 abstract boolean handle(Mail m); } static void handle(Mail m) { //按照枚举类型的声明顺序依次尝试处理 for(MailHandler handler : MailHandler.values()) if(handler.handle(m)) return; System.out.println(m + " is a dead letter"); } }
有限状态机¶
- 通常使用switch处理输入,选择下一步的状态进行状态转移
public class StateMachine {
private State currentState;
public StateMachine() {
currentState = State.START;
}
// 根据当前状态和给定的输入决定下一个状态
public void nextState(String input) {
switch (currentState) {
case START:
if (input.equals("start")) {
currentState = State.IN_PROGRESS;
} else {
currentState = State.ERROR;
}
break;
case IN_PROGRESS:
if (input.equals("pause")) {
currentState = State.PAUSED;
} else if (input.equals("finish")) {
currentState = State.FINISHED;
} else {
currentState = State.ERROR;
}
break;
case PAUSED:
if (input.equals("resume")) {
currentState = State.IN_PROGRESS;
} else if (input.equals("stop")) {
currentState = State.FINISHED;
} else {
currentState = State.ERROR;
}
break;
case FINISHED:
// 这里可能会有一些重置逻辑
break;
case ERROR:
// 处理错误
break;
}
}
public State getCurrentState() {
return currentState;
}
}
多路分发¶
- 方法的调用依赖于多个对象的动态类型(动态绑定是单路分发)
- 有几路就进行几次方法调用,每次确定一个类型
-
可以列举所有的组合,如两个参数都从3个固定类型选择,只需有枚举所有情况,即给每个类型都设置全部3种参数的成员函数
-
枚举类型分发
public enum RoShamBo2 implements Competitor <RoShamBo2> { PAPER(DRAW, LOSE, WIN), SCISSORS(WIN, DRAW, LOSE), ROCK(LOSE, WIN, DRAW); private Outcome vPAPER, vSCISSORS, vROCK; RoShamBo2(Outcome paper, Outcome scissors, Outcome rock) { this.vPAPER = paper; this.vSCISSORS = scissors; this.vROCK = rock; } @Override public Outcome compete(RoShamBo2 it) { switch(it) { default: case PAPER: return vPAPER; case SCISSORS: return vSCISSORS; case ROCK: return vROCK; } } public static void main(String [] args) { RoShamBo.play(RoShamBo2.class, 20); } }
-
常量特定方法
//即枚举所有可能的组合 PAPER { @Override public Outcome compete(RoShamBo3 it) { switch(it) { default: // To placate the compiler case PAPER: return DRAW; case SCISSORS: return LOSE; case ROCK: return WIN; } } }...
-
EnumMap
-
嵌套实现
enum RoShamBo5 implements Competitor <RoShamBo5> { PAPER, SCISSORS, ROCK; static EnumMap <RoShamBo5,EnumMap<RoShamBo5,Outcome> > table = new EnumMap <>(RoShamBo5.class); static { for(RoShamBo5 it : RoShamBo5.values()) table.put(it, new EnumMap <>(RoShamBo5.class)); initRow(PAPER, DRAW, LOSE, WIN); initRow(SCISSORS, WIN, DRAW, LOSE); initRow(ROCK, LOSE, WIN, DRAW); } static void initRow(RoShamBo5 it, Outcome vPAPER, Outcome vSCISSORS, Outcome vROCK) { EnumMap <RoShamBo5,Outcome> row = RoShamBo5.table.get(it); row.put(RoShamBo5.PAPER, vPAPER); row.put(RoShamBo5.SCISSORS, vSCISSORS); row.put(RoShamBo5.ROCK, vROCK); } @Override public Outcome compete(RoShamBo5 it) { //获取方法 return table.get(this).get(it); } public static void main(String [] args) { RoShamBo.play(RoShamBo5.class, 20); } }
-
二维数组
- 由于枚举类型可以转化为整数值,因此实际上可以直接使用二维数组
enum RoShamBo6 implements Competitor <RoShamBo6> { PAPER, SCISSORS, ROCK; private static Outcome [][] table = { { DRAW, LOSE, WIN }, // PAPER { WIN, DRAW, LOSE }, // SCISSORS { LOSE, WIN, DRAW }, // ROCK }; @Override public Outcome compete(RoShamBo6 other) { return table [this.ordinal()][other.ordinal()]; } public static void main(String [] args) { RoShamBo.play(RoShamBo6.class, 20); } }
模式匹配¶
- switch的高级用法
- 智能转型(JDK16)
static void smart(Object x) { if(x instanceof String s && s.length() > 0) { System.out.format( "%d %s%n", s.length(), s.toUpperCase()); } } //特殊例子 static void f(Object o) { if(!(o instanceof String s)) { System.out.println("Not a String"); throw new RuntimeException(); } //能到达这里说明一定是 String 类型,否则就出错了 // s is in scope here! System.out.println(s.toUpperCase()); // [1] }
- 使用
instanceof
判断类型后在接下来的作用域内就当作这个类型使用 -
即已经判断成功了,那么这个作用域内当作这个类型处理显然是安全的
-
模式匹配(JDK17)
- 允许根据数据的结构和内容来检查和分解数据。
- 对于一组具有相同基类的类型,不止希望使用基类中的公共方法
-
使用switch实现
public class PetPatternMatch { //使用非基类方法 static void careFor(Pet p) { switch(p) { case Dog d -> d.walk();//使用了只能转型 case Fish f -> f.changeWater(); case Pet sp -> sp.feed(); }; } static void petCare() { List.of(new Dog(), new Fish()) .forEach(p -> careFor(p)); } }
-
守卫:进一步细化匹配条件而不只是简单地匹配类型
- 可以是任何布尔表达式,如果类型符合并且守卫为true那么就匹配上了
System.out.println(switch(s) { case Circle c && c.area() < 100.0 -> "Small Circle: " + c; case Circle c -> "Large Circle: " + c; case Rectangle r && r.side1() == r.side2() -> "Square: " + r; case Rectangle r -> "Rectangle: " + r; });
- 上面的case对下面的具有支配性