我们有时可能会看到使用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;}@Overridepublic 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;@23ab930dProcess 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 ));}
}
打印:
CONDENSEProcess 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));}}
打印:
DEPOSITProcess 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方法。
所有文章无条件开放,顺手点个赞不为过吧!