用 enum 代替 int 常量

Q:为什么要用 enum 代替 int 常量?

首先看一下 int 枚举模式的定义方式:

1
2
3
4
5
6
7
8
public static final int A_ONE = 1;
public static final int A_TWO = 2;
public static final int A_THREE = 3;


public static final int B_ONE = 1;
public static final int B_TWO = 2;
public static final int B_THREE = 3;

这样的定义具有两个问题:

  • 如果输出这些常量值,不会看出来有任何意义
  • 很难获取到一共定义了多少这样的常量

接着看一下这样一个函数:

1
2
3
public void add(int x, int y) {
System.out.println(x + y);
}

这个函数期望传入两个都是 B 开头的常量值,但是此时传入任意 int 值编译并不会报错,且运行时也不会抛出异常,但是会造成一些莫名其妙的问题

还有一种 String 枚举模式,也许这种模式提供了可打印的常量值,但是它依赖于字符串的比较操作,这会有性能方面的问题。与此同时,如果项目中的其他成员不知道有这样的常量存在时,可能会用一些硬编码的字符串,一旦这些字符串出现拼写错误,程序并不会检测出有任何问题,只有在程序运行过程才会一些不可预测的问题

这种情况下,就需要用到枚举类来代替这种模式了,通过反编译枚举的字节码文件来看一下枚举是怎么实现的,先看一下 Java 文件:

1
2
3
4
5
6
7
8
9
public enum IntEnum {
ONE(1),
TWO(2),
THREE(3);

IntEnum(int i) {

}
}

接下来是编译后的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public final class IntEnum extends Enum {

public static IntEnum[] values() {
return (IntEnum[]) $VALUES.clone();
}

public static IntEnum valueOf(String s) {
return (IntEnum) Enum.valueOf(org / lovedev / effectivejava / _30 / IntEnum, s);
}

private IntEnum(String s, int i, int j) {
super(s, i);
}

public static final IntEnum ONE;
public static final IntEnum TWO;
public static final IntEnum THREE;
private static final IntEnum $VALUES[];

static {
ONE = new IntEnum("ONE", 0, 1);
TWO = new IntEnum("TWO", 1, 2);
THREE = new IntEnum("THREE", 2, 3);
$VALUES = (new IntEnum[]{
ONE, TWO, THREE
});
}
}

可以看出枚举是一个继承了 enum 抽象对象的 final 类,并且它的构造函数还是不可访问的,所以既不能实例化也不能扩展该类,只能访问类初始化时定义的几个 static final 实例对象

枚举还有还有一种将不同常量和行为关联起来方式,这种使用方式叫做特定于常量的方法实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public enum IntEnum {
ONE(1) {
@Override
void test() {
System.out.println("1");
}
},
TWO(2) {
@Override
void test() {
System.out.println("2");
}
},
THREE(3) {
@Override
void test() {
System.out.println("3");
}
};

IntEnum(int i) {

}

abstract void test();
}

这种方式的优点在于,每当添加一个新的枚举类型时,就必须提供一个对应的方法实现,不会因为忘记添加实现导致程序出现异常

如果每个枚举常量都对应不同的行为,这种方式显然是没有任何问题的,但是在某些情况下只需要将这些枚举常量分为两种行为,比如工作日和休息日的起床时间,此时特定于常量的方法实现就不能很好的共享代码,先看一下解决这个问题的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
enum WeekEnum {
MONDAY(WAKEUPTIME.WEEKDAY),
WEDNESDAY(WAKEUPTIME.WEEKDAY),
TUESDAY(WAKEUPTIME.WEEKDAY),
THURSDAY(WAKEUPTIME.WEEKDAY),
FRIDAY(WAKEUPTIME.WEEKDAY),
SATURDAY(WAKEUPTIME.WEEKEND),
SUNDAY(WAKEUPTIME.WEEKEND);


private WAKEUPTIME mWeekday;

WeekEnum(WAKEUPTIME weekday) {
mWeekday = weekday;
}

public int wakeUp() {
return mWeekday.wakeUp();
}


private enum WAKEUPTIME {
WEEKDAY {
@Override
int wakeUp() {
System.out.println("七点起床");
return 7;
}
},
WEEKEND {
@Override
int wakeUp() {
System.out.println("八点起床");
return 8;
}
};
abstract int wakeUp();
}
}

把起床时间的定义放到一个嵌套的枚举类中,将这个策略枚举类传入到 WeekEnum 类中,把获取每天的起床时间交给策略枚举实现

枚举具有以下几个优势:

  • 如果定义函数的参数是一个枚举类型,那么传入的参数必须是声明的枚举常量
  • 可以实现接口,定义函数以及变量

由于枚举是通过实例化对象实现的,所以对比 Int 常量,性能上会有一点缺点,不过大多数情况下不用担心这个问题