用enum代替int常量
在程序中,经常用int类型的常量表示对象的状态等信息,这种方式被称为int枚举模式,其是编译时常量,当int值发生变化时,需重新编译才能生效,如下
public static final int OFF = 0;
public static final int STANDBY = 1;
public static final int ON = 2;
每个enum默认继承java.lang.Enum,内部的enum实例都为公有静态常量,可直接通过类名访问
public enum State {
OFF, STANDBY, ON;
}
方法values()将内部实例转为数组,ordinal()可获取实例位置(从0开始),valueOf()返回指定字符串所对应的Enum实例
State[] values = State.values();
for (State value : values) {
System.out.println(value + " at index " + value.ordinal());
}
System.out.println(State.valueOf("OFF").getClass().getSimpleName());
如上打印(toString默认打印实例名)
OFF at index 0
STANDBY at index 1
ON at index 2
State
enum介绍
不同枚举可包含同名实例,因为每个枚举都有自己的命名空间,当新增某个值时也无需对其重新编译
public enum TVState {
OFF, STANDBY, ON;
}
public enum DVDState {
OFF, STANDBY, ON;
}
如上如果为int常量则需加上前缀作为区分,若将DVD_STATE_OFF传入需要TV_STATE_OFF的函数,也不会有任何问题,因为不会对int进行类型检查
public static final int TV_STATE_OFF = 0;
public static final int TV_STATE_STANDBY = 1;
public static final int TV_STATE_ON = 2;
public static final int DVD_STATE_OFF = 0;
public static final int DVD_STATE_STANDBY = 1;
public static final int DVD_STATE_ON = 2;
枚举还可以声明域(均为final,需放在后面)、方法(内部的每个对象都要实现其抽象方法和构造函数)
public enum Operation {
PLUS("+") {
public double apply(double x, double y) {
return x + y;
}
},
MINUS("-") {
public double apply(double x, double y) {
return x - y;
}
},
TIMES("*") {
public double apply(double x, double y) {
return x * y;
}
},
DIVIDE("/") {
public double apply(double x, double y) {
return x / y;
}
};
private final String symbol;
Operation(String symbol) {
this.symbol = symbol;
}
public abstract double apply(double x, double y);
}
调用过程如下
for (Operation operation : Operation.values()) {
System.out.println("4.0" + operation + "2.0" + "=" + operation.apply(4.0, 2.0));
}
打印如下
4.0+2.0=6.0
4.0-2.0=2.0
4.0*2.0=8.0
4.0/2.0=2.0
避免使用ordinal()
ordinal()可返回枚举实例的位置,其从0开始
- 除非确定实例的位置与顺序不变,不然应该避免使用ordinal()
- 也不能由ordinal()导出相关值,而应该将关联的值保存在实例域中
public enum State {
OFF(1), STANDBY(2), ON(3);
private final int index;
State(int index) {
this.index = index;
}
public int getIndex() {
return index;
}
}
策略枚举
如下是计算当日工资的枚举,基本工资为10块,超过8小时为1.5倍,周末为2倍
enum PayDay {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
double base = 10;
double pay(int hours) {
switch (this) {
case SATURDAY:
case SUNDAY:
return hours * base * 2;
default:
return 8 * base + ((hours > 8) ? (hours - 8) * base * 1.5 : 0);
}
}
}
当需要增加一个Holiday(3倍工资),需同步增加switch语句,如果忘记则走default,为了强制实现在每次添加新实例都选择一个计算策略,可采用如下的策略枚举
enum PayDay {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY(PayType.WEEKEND), SUNDAY(PayType.WEEKEND);
private final PayType payType;
PayDay(PayType payType) {
this.payType = payType;
}
PayDay() {
this(PayType.WEEKDAY);
}
private enum PayType {
WEEKDAY {
double pay(int hours) {
return hours * base * 2;
}
},
WEEKEND {
double pay(int hours) {
return 8 * base + ((hours > 8) ? (hours - 8) * base * 1.5 : 0);
}
};
private static double base = 10;
abstract double pay(int hours);
}
}
用EnumSet代替位域
如下有用于表示方向的位域
public static final int EAST = 1 << 0;
public static final int WEST = 1 << 1;
public static final int SOUTH = 1 << 2;
public static final int NORTH = 1 << 3;
private int direction = 0;
public void setDirection(int direction) {
this.direction = direction;
}
public int getDirection() {
return direction;
}
当需要表示东南方向时,需要传递 EAST | SOUTH,打印输出5显示不是可读的结果
setDirection(EAST | SOUTH);
System.out.println(getDirection());
当转变为使用Enum
enum Direction {
EAST, WEST, SOUTH, NORTH;
}
利用EnumSet存储Enum并打印输出[EAST, SOUTH],可见其有更好的灵活性,同时EnumSet具有集合的优点,内部采用位运算具有较佳的效率
EnumSet<Direction> direction = EnumSet.of(Direction.EAST, Direction.SOUTH);
System.out.println(direction);
用EnumMap代替ordinal()索引映射
对于下面的类Plant及内部枚举Type
class Plant {
enum Type {TREE, FLOWER, GLASS}
final String name;
final Type type;
public Plant(String name, Type type) {
this.name = name;
this.type = type;
}
@NonNull
@Override
public String toString() {
return name;
}
}
存在下面的实例
List<Plant> garden = new ArrayList<>();
garden.add(new Plant("A", Plant.Type.TREE));
garden.add(new Plant("B", Plant.Type.FLOWER));
garden.add(new Plant("C", Plant.Type.GLASS));
garden.add(new Plant("D", Plant.Type.TREE));
garden.add(new Plant("E", Plant.Type.FLOWER));
garden.add(new Plant("F", Plant.Type.GLASS));
若要对其分类,如下创建Set[],以枚举的ordinal()为索引,将实例分类存储到set
Set<Plant>[] plantsOrder = new Set[Plant.Type.values().length];
for (int i = 0; i < plantsOrder.length; i++) {
plantsOrder[i] = new HashSet<>();
}
for (Plant p : garden) {
plantsOrder[p.type.ordinal()].add(p);
}
for (int i = 0; i < plantsOrder.length; i++) {
System.out.printf("%s:%s\n", Plant.Type.values()[i], plantsOrder[i]);
}
打印如下
TREE:[A, D]
FLOWER:[E, B]
GLASS:[F, C]
如上虽然可行,但有许多问题
- 数组不能与泛型共用,存在Unchecked异常
- 数组不知道索引代表的意义(如上0存储的为TREE枚举),需要人为去处理
而使用EnumMap可避免上述问题,以Enum为键进行存储,而不是以它的ordinal(),如果是 多维的,可以使用 EnumMap<…,EnumMap<…>>
Map<Plant.Type, Set<Plant>> plantsOrder = new EnumMap<>(Plant.Type.class);
for (Plant.Type type : Plant.Type.values()) {
plantsOrder.put(type, new HashSet<>());
}
for (Plant p : garden) {
plantsOrder.get(p.type).add(p);
}
System.out.println(plantsOrder);
打印如下
{TREE=[A, D], FLOWER=[E, B], GLASS=[F, C]}
用接口模拟可扩展的枚举
如对于上面介绍的Operation枚举,可定义接口将其分离
interface Operation {
double apply(double x, double y);
}
enum BasicOperation implements Operation {
PLUS("+") {
public double apply(double x, double y) {
return x + y;
}
},
MINUS("-") {
public double apply(double x, double y) {
return x - y;
}
},
TIMES("*") {
public double apply(double x, double y) {
return x * y;
}
},
DIVIDE("/") {
public double apply(double x, double y) {
return x / y;
}
};
private final String symbol;
BasicOperation(String symbol) {
this.symbol = symbol;
}
}
当需要扩展枚举时,可再定义一个枚举实现接口
enum ExtendedOperation implements Operation {
EXP("^") {
public double apply(double x, double y) {
return x + y;
}
},
REMAINDER("%") {
public double apply(double x, double y) {
return x / y;
}
};
private final String symbol;
ExtendedOperation(String symbol) {
this.symbol = symbol;
}
}