第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列表中,出错的概率很小。

posted @ 2016-08-02 23:28  没有梦想的小灰灰  阅读(381)  评论(0编辑  收藏  举报