使用 EnumMap 替代序数索引
有时可能会看到使用 ordinal 方法来索引到数组或列表的代码。 例如,考虑一
下这个简单的类来代表一种植物:
class Plant {
enum LifeCycle { ANNUAL, PERENNIAL, BIENNIAL }
final String name;
final LifeCycle lifeCycle;
Plant(String name, LifeCycle lifeCycle) {
this.name = name;
this.lifeCycle = lifeCycle;
}
@Override public String toString() {
return name;
}
}
假设你有一组植物代表一个花园,想要列出这些由生命周期组织的植物 (一年生,多年生,或
双年生)。为此,需要构建三个集合,每个生命周期作为一个,并遍历整个花园,将每个植物放置在适当
的集合中。一些程序员可以通过将这些集合放入一个由生命周期序数索引的数组中来实现这一点
这种方法是有效的,但充满了问题。 因为数组不兼容泛型(详见第 28 条),程序需要一个未经检
查的转换,并且不会干净地编译。 由于该数组不知道索引代表什么,因此必须手动标记索引输出。 但
是这种技术最严重的问题是,当你访问一个由枚举序数索引的数组时,你有责任使用正确的 int 值;
int 不提供枚举的类型安全性。 如果你使用了错误的值,程序会默默地做错误的事情,如果你幸运的
话,抛出一个 ArrayIndexOutOfBoundsException 异常
有一个更好的方法来达到同样的效果。 该数组有效地用作从枚举到值的映射,因此不妨使用
Map 。 更具体地说,有一个非常快速的 Map 实现,设计用于枚举键,称为
java.util.EnumMap 。 下面是当程序重写为使用 EnumMap 时的样子:
// Using an EnumMap to associate data with an enum
Map<Plant.LifeCycle, Set<Plant>> plantsByLifeCycle =
new EnumMap<>(Plant.LifeCycle.class);
for (Plant.LifeCycle lc : Plant.LifeCycle.values())
plantsByLifeCycle.put(lc, new HashSet<>());
for (Plant p : garden)
plantsByLifeCycle.get(p.lifeCycle).add(p);
System.out.println(plantsByLifeCycle);
这段程序更简短,更清晰,更安全,运行速度与原始版本相当。 没有不安全的转换; 无需手动标记
输出,因为 map 键是知道如何将自己转换为可打印字符串的枚举; 并且不可能在计算数组索引时出
错。 EnumMap 与序数索引数组的速度相当,其原因是 EnumMap 内部使用了这样一个数组,但它对
程序员的隐藏了这个实现细节,将 Map 的丰富性和类型安全性与数组的速度相结合。 请注意,
EnumMap 构造方法接受键类 Class 型的 Class 对象:这是一个有限定的类型令牌(bounded type
token),它提供运行时的泛型类型信息