有时可能会看到使用 ordinal 方法(条目 35)来索引到数组或列表的代码。 例如,考虑一下这个简单的类来代表一种植物:
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;}
}
现在假设你有一组植物代表一个花园,想要列出这些由生命周期组织的植物 (一年生,多年生,或双年生)。为此,需要构建三个集合,每个生命周期作为一个,并遍历整个花园,将每个植物放置在适当的集合中。
// Using ordinal() to index into an array - DON'T DO THIS!
Set<Plant>[] plantsByLifeCycle = (Set<Plant>[]) new Set[Plant.LifeCycle.values().length];
for (int i = 0; i < plantsByLifeCycle.length; i++)plantsByLifeCycle[i] = new HashSet<>();for (Plant p : garden)plantsByLifeCycle[p.lifeCycle.ordinal()].add(p);// Print the results
for (int i = 0; i < plantsByLifeCycle.length; i++) {System.out.printf("%s: %s%n",Plant.LifeCycle.values()[i], plantsByLifeCycle[i]);
}
这种方法是有效的,但充满了问题。 因为数组不兼容泛型(条目 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),它提供运行时的泛型类型信息(条目 33)。
通过使用 stream (条目 45)来管理 Map ,可以进一步缩短以前的程序。 以下是最简单的基于 stream 的代码,它们在很大程度上重复了前面示例的行为:
// Naive stream-based approach - unlikely to produce an EnumMap!
System.out.println(Arrays.stream(garden).collect(groupingBy(p -> p.lifeCycle)));
这个代码的问题在于它选择了自己的 Map 实现,实际上它不是 EnumMap ,所以它不会与显式 EnumMap 的版本的空间和时间性能相匹配。 为了解决这个问题,使用 Collectors.groupingBy 的三个参数形式的方法,它允许调用者使用 mapFactory 参数指定 map 的实现:
// Using a stream and an EnumMap to associate data with an enum
System.out.println(Arrays.stream(garden).collect(groupingBy(p -> p.lifeCycle,() -> new EnumMap<>(LifeCycle.class), toSet())));
这样的优化在像这样的示例程序中是不值得的,但是在大量使用 Map 的程序中可能是至关重要的。