枚举
枚举类型
枚举类型(Enumerated Type)很早就出现在编程语言中,它被用来将一组类似的值包含到一种类型当中。而这种枚举类型的名称则会被定义成独一无二的类型描述符,在这一点上和常量的定义相似。不过相比较常量类型,枚举类型可以为申明的变量提供更大的取值范围。为了改进 Java 语言在这方面的不足弥补缺陷,5.0 版本 SDK 发布时候,在语言层面上增加了枚举类型。枚举类型的定义也非常的简单,用 enum 关键字加上名称和大括号包含起来的枚举值体即可:
// 定义一周七天的枚举类型
public enum WeekDay { Mon, Tue, Wed, Thu, Fri, Sat, Sun }
// 读取当天的信息
WeekDay today = readToday();
// 根据日期来选择进行活动
switch(today) {
Mon: do something; break;
Tue: do something; break;
Wed: do something; break;
Thu: do something; break;
Fri: do something; break;
Sat: play sports game; break;
Sun: have a rest; break;
}
最直接的益处就是扩大 switch 语句使用范围。5.0 之前,Java 中 switch 的值只能够是简单类型,比如 int、byte、short、char, 有了枚举类型之后,就可以使用对象了。需要注意的是,Java 中的枚举类型实际上会被编译为类文件,值即是这个类型的成员变量,譬如我们丰富下前文的枚举类型:
public enum WeekDay {
Mon("Monday"),
Tue("Tuesday"),
Wed("Wednesday"),
Thu("Thursday"),
Fri("Friday"),
Sat("Saturday"),
Sun("Sunday");
private final String day;
private WeekDay(String day) {
this.day = day;
}
public static void printDay(int i) {
switch (i) {
case 1:
System.out.println(WeekDay.Mon);
break;
// ...
default:
System.out.println("wrong number!");
}
}
public String getDay() {
return day;
}
}
枚举值关联
我们可以创建如下的枚举类型:
public enum Element {
H("Hydrogen"),
HE("Helium"),
// ...
NE("Neon");
public final String label;
private Element(String label) {
this.label = label;
}
}
首先,我们注意到声明列表中的特殊语法。这就是枚举类型的构造函数被调用的方式。尽管对枚举类型使用 new 操作符是非法的,但我们可以在声明列表中传递构造器参数。
然后我们声明一个实例变量 label。这里面有几件事需要注意。
- 首先,我们选择了标签标识符而不是名称。尽管成员字段名可以使用,但我们还是选择了 label,以避免与预定义的 Enum.name()方法相混淆。
- 第二,我们的标签字段是 final 的。虽然枚举的字段不一定是 finals 的,但在大多数情况下我们不希望我们的标签发生变化。本着枚举值是恒定的精神,这是有道理的。
- 最后,标签字段是 public 的,所以我们可以直接访问标签。
System.out.println(BE.label);
另一方面,该字段可以是私有的,用 getLabel()方法访问。为了简洁起见,本文将继续使用公共字段的样式。Java 为所有枚举类型提供了一个 valueOf(String)方法。因此,我们总是可以根据声明的名称得到一个枚举的值。
assertSame(Element.LI, Element.valueOf("LI"));
然而,我们可能也想通过我们的标签字段来查询一个枚举值。要做到这一点,我们可以添加一个静态方法。
public static Element valueOfLabel(String label) {
for (Element e : values()) {
if (e.label.equals(label)) {
return e;
}
}
return null;
}
static valueOfLabel()方法遍历元素值,直到找到一个匹配的元素。如果没有找到匹配的,它就返回 null。反之,可以抛出一个异常,而不是返回 null。
assertSame(Element.LI, Element.valueOfLabel("Lithium"));
为了提高值查询的效率,我们还可以添加如下的缓存:
public enum Element {
H("Hydrogen", 1, 1.008f),
HE("Helium", 2, 4.0026f),
// ...
NE("Neon", 10, 20.180f);
private static final Map<String, Element> BY_LABEL = new HashMap<>();
private static final Map<Integer, Element> BY_ATOMIC_NUMBER = new HashMap<>();
private static final Map<Float, Element> BY_ATOMIC_WEIGHT = new HashMap<>();
static {
for (Element e : values()) {
BY_LABEL.put(e.label, e);
BY_ATOMIC_NUMBER.put(e.atomicNumber, e);
BY_ATOMIC_WEIGHT.put(e.atomicWeight, e);
}
}
public final String label;
public final int atomicNumber;
public final float atomicWeight;
private Element(String label, int atomicNumber, float atomicWeight) {
this.label = label;
this.atomicNumber = atomicNumber;
this.atomicWeight = atomicWeight;
}
public static Element valueOfLabel(String label) {
return BY_LABEL.get(label);
}
public static Element valueOfAtomicNumber(int number) {
return BY_ATOMIC_NUMBER.get(number);
}
public static Element valueOfAtomicWeight(float weight) {
return BY_ATOMIC_WEIGHT.get(weight);
}
}