mirror of
https://github.com/sjsdfg/effective-java-3rd-chinese.git
synced 2025-03-24 08:10:32 +08:00
37. 使用 EnumMap 替代序数索引
This commit is contained in:
parent
945a6e4f14
commit
df58b331f0
@ -2,7 +2,7 @@
|
||||
|
||||
有时可能会看到使用 `ordinal` 方法(条目 35)来索引到数组或列表的代码。 例如,考虑一下这个简单的类来代表一种植物:
|
||||
|
||||
```
|
||||
```Java
|
||||
class Plant {
|
||||
enum LifeCycle { ANNUAL, PERENNIAL, BIENNIAL }
|
||||
final String name;
|
||||
@ -21,7 +21,7 @@ class Plant {
|
||||
|
||||
现在假设你有一组植物代表一个花园,想要列出这些由生命周期组织的植物 (一年生,多年生,或双年生)。为此,需要构建三个集合,每个生命周期作为一个,并遍历整个花园,将每个植物放置在适当的集合中。一些程序员可以通过将这些集合放入一个由生命周期序数索引的数组中来实现这一点:
|
||||
|
||||
```
|
||||
```Java
|
||||
// Using ordinal() to index into an array - DON'T DO THIS!
|
||||
Set<Plant>[] plantsByLifeCycle =
|
||||
(Set<Plant>[]) new Set[Plant.LifeCycle.values().length];
|
||||
@ -43,7 +43,7 @@ for (int i = 0; i < plantsByLifeCycle.length; i++) {
|
||||
|
||||
有一个更好的方法来达到同样的效果。 该数组有效地用作从枚举到值的映射,因此不妨使用 `Map`。 更具体地说,有一个非常快速的 `Map` 实现,设计用于枚举键,称为 `java.util.EnumMap`。 下面是当程序重写为使用 `EnumMap` 时的样子:
|
||||
|
||||
```
|
||||
```Java
|
||||
// Using an EnumMap to associate data with an enum
|
||||
Map<Plant.LifeCycle, Set<Plant>> plantsByLifeCycle =
|
||||
new EnumMap<>(Plant.LifeCycle.class);
|
||||
@ -61,7 +61,7 @@ System.out.println(plantsByLifeCycle);
|
||||
|
||||
通过使用 `stream`(条目 45)来管理 `Map`,可以进一步缩短以前的程序。 以下是最简单的基于 `stream` 的代码,它们在很大程度上重复了前面示例的行为:
|
||||
|
||||
```
|
||||
```Java
|
||||
// Naive stream-based approach - unlikely to produce an EnumMap!
|
||||
System.out.println(Arrays.stream(garden)
|
||||
.collect(groupingBy(p -> p.lifeCycle)));
|
||||
@ -69,7 +69,7 @@ System.out.println(Arrays.stream(garden)
|
||||
|
||||
这个代码的问题在于它选择了自己的 `Map` 实现,实际上它不是 `EnumMap`,所以它不会与显式 `EnumMap` 的版本的空间和时间性能相匹配。 为了解决这个问题,使用 `Collectors.groupingBy` 的三个参数形式的方法,它允许调用者使用 mapFactory 参数指定 map 的实现:
|
||||
|
||||
```
|
||||
```Java
|
||||
// Using a stream and an EnumMap to associate data with an enum
|
||||
System.out.println(Arrays.stream(garden)
|
||||
.collect(groupingBy(p -> p.lifeCycle,
|
||||
@ -81,8 +81,8 @@ System.out.println(Arrays.stream(garden)
|
||||
基于 `stream` 版本的行为与 `EmumMap` 版本的行为略有不同。 `EnumMap` 版本总是为每个工厂生命周期生成一个嵌套 `map` 类,而如果花园包含一个或多个具有该生命周期的植物时,则基于流的版本才会生成嵌套 `map` 类。 因此,例如,如果花园包含一年生和多年生植物但没有两年生的植物,`plantByLifeCycle` 的大小在 `EnumMap` 版本中为三个,在两个基于流的版本中为两个。
|
||||
|
||||
你可能会看到数组索引 (两次) 的数组,用序数来表示从两个枚举值的映射。例如,这个程序使用这样一个数组来映射两个阶段到一个阶段转换(phase transition)(液体到固体表示凝固,液体到气体表示沸腾等等):
|
||||
|
||||
```
|
||||
|
||||
```Java
|
||||
// Using ordinal() to index array of arrays - DON'T DO THIS!
|
||||
public enum Phase {
|
||||
SOLID, LIQUID, GAS;
|
||||
@ -108,7 +108,7 @@ public enum Phase {
|
||||
|
||||
同样,可以用 `EnumMap` 做得更好。 因为每个阶段转换都由一对阶段枚举来索引,所以最好将关系表示为从一个枚举(from 阶段)到第二个枚举(to 阶段)到结果(阶段转换)的 `map`。 与阶段转换相关的两个阶段最好通过将它们与阶段转换枚举相关联来捕获,然后可以用它来初始化嵌套的 EnumMap:
|
||||
|
||||
```
|
||||
```Java
|
||||
// Using a nested EnumMap to associate data with enum pairs
|
||||
public enum Phase {
|
||||
SOLID, LIQUID, GAS;
|
||||
@ -143,7 +143,7 @@ public enum Phase {
|
||||
|
||||
现在假设想为系统添加一个新阶段:等离子体或电离气体。 这个阶段只有两个转变:电离,将气体转化为等离子体; 和去离子,将等离子体转化为气体。 要更新基于数组的程序,必须将一个新的常量添加到 `Phase`,将两个两次添加到 `Phase.Transition`,并用新的十六个元素版本替换原始的九元素阵列数组。 如果向数组中添加太多或太少的元素或者将元素乱序放置,那么如果运气不佳:程序将会编译,但在运行时会失败。 要更新基于 `EnumMap` 的版本,只需将 `PLASMA` 添加到阶段列表中,并将 `IONIZE(GAS, PLASMA)` 和 `DEIONIZE(PLASMA, GAS)` 添加到阶段转换列表中:
|
||||
|
||||
```
|
||||
```Java
|
||||
// Adding a new phase using the nested EnumMap implementation
|
||||
public enum Phase {
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user