第33条:用EnumMap代替序数索引
有时候,会见到利用ordinal方法来索引数组的代码。例如下面这个简化的类,表示一种烹饪用的香草:
public class Herb { public enum Type { ANNUAL, PERENNIAL, BIENNIAL } private final String name; private final Type type; Herb(String name, Type type) { this.name = name; this.type = type; } @Override public String toString() { return name; } }
假设有一个香草的数组,表示一座花园中的植物,想要按照类型(一年生、多年生或者两年生植物)进行组织后将植物列出来。
1.将集合放到一个按照类型的序数进行索引的数组中实现:
public static void main(String[] args) { Herb[] garden = {new Herb("a", Herb.Type.ANNUAL), new Herb("b", Herb.Type.BIENNIAL)}; Set<Herb>[] herbsByType = (Set<Herb>[]) new Set[Herb.Type.values().length]; for(int i=0; i < herbsByType.length; i++) herbsByType[i] = new HashSet<Herb>(); for(Herb h : garden) herbsByType[h.type.ordinal()].add(h); for(int i=0; i < herbsByType.length; i++) System.out.printf("%s: %s%n", Herb.Type.values()[i], herbsByType[i]); }
这种方法可行,但是由于数组与泛型不兼容,需要进行未受检的转换。
2.更好的方法,数组实际上充当从枚举到值的映射,EnumMap提供这样的实现:
public static void main(String[] args) { Herb[] garden = {new Herb("a", Herb.Type.ANNUAL), new Herb("b", Herb.Type.BIENNIAL)}; Map<Herb.Type, Set<Herb>> herbByType = new EnumMap<Herb.Type, Set<Herb>>(Herb.Type.class); for(Herb.Type t : Herb.Type.values()) herbByType.put(t, new HashSet<Herb>()); for(Herb h : garden) herbByType.get(h.type).add(h); System.out.println(herbByType); }
不存在不安全的类型转换。
还可能遇到按照序数进行两次索引的数组的数组
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()]; } } }
利用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); } } }
不需要平方级大小的数组。
如果增加一个新的阶段plasma(离子),只有两个过渡与这个阶段关联,电离化,将气体变成离子,消电离化,将离子变成气体。
基于数组的程序,必须给Phase添加一个PLASMA常量,给Phase.Transition添加两种常量,用新的16个元素的版本取代原来9个元素的数组的数组,这很容易出错。
基于EnumMap的版本,所要做的只是将PLASMA添加到Phase列表,并把IONIZE(GAS, PLASMA), DEIONIZE(PLASMA, GAS) 添加到Phase.Transition列表中,出错的概率很小。