我们有时可能会看到使用ordinal方法来索引数组或者列表的代码。例如,考虑到下面这个简单的类,用于表示一种植物:
public class Plant {
enum LifeCycle {ANNUAL, PERENNIAL, BIENNIAL}
final String name;
final LifeCycle lifeCycle;
public Plant(String name, LifeCycle lifeCycle) {
this.name = name;
this.lifeCycle = lifeCycle;
}
@Override
public String toString() {
return name;
}
}
现在假设有一个表示花园的植物数组,我们想按照生长周期(一年、多年生或者两年生)列出这些植物。要做到这点,我们要构建三个集合,每个生长周期一个,然后遍历整个花园,将每个植物放入相应的集合中。有些程序员会这么做:把这些集合放在一个数组中,以生长周期的序号来索引。
public static void main(String[] args) {
List<Plant> garden = new ArrayList<>();
garden.add(new Plant("oneYear", Plant.LifeCycle.ANNUAL));
garden.add(new Plant("twoYears", Plant.LifeCycle.PERENNIAL));
garden.add(new Plant("twoYears", Plant.LifeCycle.PERENNIAL));
garden.add(new Plant("manyYears", Plant.LifeCycle.BIENNIAL));
garden.add(new Plant("manyYears", Plant.LifeCycle.BIENNIAL));
garden.add(new Plant("oneYear", Plant.LifeCycle.ANNUAL));
// 不推荐使用ordinal()
Set<Plant>[] plantsOrder = new Set[Plant.LifeCycle.values().length];
for (int i = 0; i < plantsOrder.length; i++) {
plantsOrder[i] = new HashSet<>();
}
for (Plant p : garden) {
plantsOrder[p.lifeCycle.ordinal()].add(p);
}
for (int i = 0; i < plantsOrder.length; i++) {
System.out.printf("%s:%s\n", Plant.LifeCycle.values()[i], plantsOrder[i]);
}
System.out.println(plantsOrder);
}
打印:
ANNUAL:[oneYear, oneYear]
PERENNIAL:[twoYears, twoYears]
BIENNIAL:[manyYears, manyYears]
[Ljava.util.Set;@23ab930d
Process finished with exit code 0
如上虽然可行,但有许多问题
- 数组不能与泛型共用,存在Unchecked异常
- 数组不知道索引代表的意义(如上0存储的为ANNUAL枚举),需要人为去处理
而使用EnumMap可避免上述问题,以Enum为键进行存储,而不是以它的ordinal(),如果是 多维的,可以使用 EnumMap<…,EnumMap<…>>
Map<Plant.LifeCycle, Set<Plant>> plantsOrder = new EnumMap<>(Plant.LifeCycle.class);
for (Plant.LifeCycle type : Plant.LifeCycle.values()) {
plantsOrder.put(type, new HashSet<>());
}
for (Plant p : garden) {
plantsOrder.get(p.lifeCycle).add(p);
}
System.out.println(plantsOrder);
打印:
{ANNUAL=[oneYear, oneYear], PERENNIAL=[twoYears, twoYears], BIENNIAL=[manyYears, manyYears]}
Process finished with exit code 0
注意:enumMap构造器接收的是其键的类型对应的class对象:这是一个有限制的类型令牌,它提供了运行时的泛型信息。
有时候我们还会看到使用序号来索引的数组的数组(序号用了两次),用来表示来自两个枚举值的映射。例如下面的程序,它使用了这样的一个数组来将两个相映射到一个相变(从液态到固态是凝固,从液态到气态是沸腾的,等等):
// 不推荐这么做
public enum Phase {
SOLID, LIQUID, GAS;
public enum Transition {
MELT, FREEZE, BOTL, CONDENSE, SUBLIME, DEPOSIT;
private static final Transition[][] TRANSITIONS = {
{null, MELT, SUBLIME},
{FREEZE, null, BOTL},
{DEPOSIT, CONDENSE, null}
};
public static Transition from(Phase src, Phase dst) {
return TRANSITIONS[src.ordinal()][dst.ordinal()];
}
}
public static void main(String[] args) {
System.out.println(Transition.from(GAS,LIQUID ));
}
}
打印:
CONDENSE
Process finished with exit code 0
这段代码的意思是说,要进行二维数组组合,通过方法去取对应的值,看起来没问题,但实际上如果和第一个案例差不多,并且如果添加想新的植物,扩展不太方便,同理,可以使用EnumMap来修改:
public enum Phase {
SOLID, LIQUID, GAS;
public enum Transition {
MELT(SOLID,LIQUID), FREEZE(LIQUID, SOLID),
BOIL(LIQUID, GAS), CONDENSE(GAS, LIQUID),
SUBLIME(SOLID, GAS), DEPOSIT(GAS, SOLID);
private final Phase src;
private final Phase dst;
Transition(Phase src, Phase dst) {
this.src = src;
this.dst = dst;
}
private static final Map<Phase, Map<Phase, Transition>> m =
new EnumMap<Phase, Map<Phase, Transition>>(Phase.class);
static {
for(Phase p : Phase.values())
m.put(p, new EnumMap<Phase, Transition>(Phase.class));
for(Transition t : Transition.values())
m.get(t.src).put(t.dst, t);
}
public static Transition from(Phase src, Phase dst) {
return m.get(src).get(dst);
}
}
public static void main(String[] args) {
System.out.println(Transition.from(GAS, SOLID));
}
}
打印:
DEPOSIT
Process finished with exit code 0
我们也可以使用嵌套的enumMap实现添加一个新的相
SOLID, LIQUID, GAS, PLASMA;
public enum Transition {
MELT(SOLID,LIQUID), FREEZE(LIQUID, SOLID),
BOIL(LIQUID, GAS), CONDENSE(GAS, LIQUID),
SUBLIME(SOLID, GAS), DEPOSIT(GAS, SOLID),
IONIZE(GAS,PLASMA),DEIONZE(PLASMA,GAS);
...//其他代码不变
总而言之,序号不太适合用来索引数组,应该使用enumMap代替。程序员应该极少或绝对不使用ordinal方法。
所有文章无条件开放,顺手点个赞不为过吧!