第30条:用enum代替int常量

在编程语言中还没有引入枚举类型之前,表示枚举类型的常量模式是声明一组具名的int常量,每个类型成员一个常量:

1
2
3
4
5
6
7
public static final int APPLE_FUJI = 0;
public static final int APPLE_PIPPIN = 1;
public static final int APPLE_GRANNY_SMITH = 2;

public static final int ORANGE_NAVEL = 0;
public static final int ORANGE_TEMPLE = 1;
public static final int ORANGE_BLOOD = 2;

这种方法称作int枚举模式。在这种模式中使用String常量,而不是int常量。这样的变体被称为String枚举模式

Java 1.5增加了枚举类型,可以避免intString枚举模式的缺点。

1
2
public enum Apple {FUJI, PIPPIN, GRANNY_SMITH}
public enum ORANGE {NAVEL, TEMPLE, BLOOD}

举个枚举类型的好例子:

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
public enum Planet {
MERCURY(3.302e+23, 2.439e6),
VENUS (4.869e+24, 6.052e6),
EARTH (5.975e+24, 6.378e6),
MARS (6.419e+23, 3.393e6),
JUPITER(1.899e+27, 7.149e7),
SATURN (5.685e+26, 6.027e7),
URANUS (8.683e+25, 2.556e7),
NEPTUNE(1.024e+26, 2.477e7);

private final double mass; // In kilograms
private final double radius; // In meters
private final double surfaceGravity; // In m / s^2

// Universal gravitational constant in m^3 / kg s^2
private static final double G = 6.67300E-11;

// Constructor
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
surfaceGravity = G * mass / (radius * radius);
}

public double mass() { return mass; }
public double radius() { return radius; }
public double surfaceGravity() { return surfaceGravity; }

public double surfaceWeight(double mass) {
return mass * surfaceGravity; // F = ma
}
}

下面是一个简短的程序,根据某个物体在地球上的重量,打印出该物体在所有8颗行星上的重量:

1
2
3
4
5
6
7
8
9
10
// Takes earth-weight and prints table of weights on all planets (Page 160)
public class WeightTable {
public static void main(String[] args) {
double earthWeight = Double.parseDouble(args[0]);
double mass = earthWeight / Planet.EARTH.surfaceGravity();
for (Planet p : Planet.values())
System.out.printf("Weight on %s is %f%n",
p, p.surfaceWeight(mass));
}
}

每个Plant常量都关联了不同的数据,但你有时需要将本质上不同的行为与每个常量关联起来。

第31条:用实例域代替序数

许多枚举天生就与一个单独的int值相关联。所有的枚举都有一个ordinal方法,它返回每个枚举常量在类型中的数字位置。你可以试着从序数中得到关联的int值。

1
2
3
4
5
6
7
8
public enum Ensemble {
SOLO, DUET, TRIO, QUARTET, QUINTET,
SEXTET, SEPTET, OCTET, NONET, DECTET;

public int numberOfMusicians() {
return ordinal() + 1;
}
}

虽然这个枚举不错,但是维护起来就像一场噩梦。如果常量进行重新排列,numberOfMusicians方法就会遭到破坏。如果要再添加一个与已经用过的int值关联的枚举常量,就没那么走运了。例如,给双四重奏(double quartet)添加一个常量,它就像个八重奏一样,是由8位演奏家组成,但是没有办法做到。

永远不要根据枚举的序数导出与它关联的值,而是要将它保存在一个实例域中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public enum Ensemble {
SOLO(1), DUET(2), TRIO(3), QUARTET(4), QUINTET(5),
SEXTET(6), SEPTET(7), OCTET(8), DOUBLE_QUARTET(8),
NONET(9), DECTET(10),TRIPLE_QUARTET(12);

private final int numberOfMusicians;

Ensemble(int size) {
this.numberOfMusicians = size;
}

public int numberOfMusicians() {
return numberOfMusicians;
}
}

Enum规范中谈到ordinal时这么写道:“大多数程序要都不需要这个方法。它是设计成用于像EnumSetEnumMap这种基于枚举的通用数据结构的。”除非你再编写的是这种数据结构,否则最好完全避免使用ordinal方法。

第32条:用EnumSet替代位域

第33条:用EnumMap替代序数索引

第34条:用接口模拟可伸缩的枚举

第35条:注解优先于命名模式

第36条:坚持使用Override注解

第37条:用标记接口定义类型