【笔记37】使用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.将集合放到一个按照类型的序数进行索引的数组中实现:

//Using ordinal() to index an array -Don't Do This!
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提供这样的实现:

//Using an EnumMap to associate data with an enum
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);
}

程序更简短、更清楚、更安全,运行速度和使用序数的程序媲美。不存在不安全的类型转换。不必手工标注这些索引的输出,计算索引时也不会发生错误。

EnumMap在运行速度方面之所以能与通过序数索引的数组相媲美,是因为EnumMap在内部使用了这种数组。但是他对程序员隐藏了这种实现细节,集Map的丰富功能和类型安全与数组的快速于一身。注意EnumMap构造器采用键类型的Class对象:这是一个有限制的类型令牌(bounded type token),他提供了运行时的泛型信息。

下面这个程序就是使用一个数组将两个阶段映射到一个阶段过渡中(从液体到固体称作凝固,从液体到气体称作沸腾,诸如此类)。

//Using a nested EnumMap to associate data with enum pairs
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 trans : Transition.values()) {
                m.get(trans.src).put(trans.dst, trans);
            }
        }
 
        public static Transition from(Phase _src, Phase _dst) {
            return m.get(_src).get(_dst);
        }
    }
}

map的类型为Map<Phase,<Map<Phase, Transition>>,表示是由键为源Phase(即第一个phase)、值为另一个map组成的Map,其中组成值的Map是由键值对目标Phase(即第二个Phase)、Transition组成的。静态初始化代码块中的第一个循环块中的第一个循环初始化了外部map,得到了三个空的内容map。代码块中的第二个循环利用每个状态过渡常量提供的起始信息和目标信息初始化了内部map。

现在假设想要给系统添加一个新的阶段:plasma(离子)或者电离气体。只有两个过渡与这个阶段关联:电离化,他将气体变成离子;以及消电离化,将例子变成气体。为了更新基于数组的程序,必须给Phase添加一种新常量,给Phase.Transition添加两种新常量,用一种新的16个元素的版本取待原来9个元素的数组的数组。如果给数组添加的元素过多或者过少,或者元素放置不妥当,可就麻烦了:程序可以编译,但是会在运行时失败。为了更新基于EnumMap的版本,所要做的是就是必须将PLASMA添加到Phase列表,并将IONIZE(GAS,PLASMA)和DEIONIZE(PLASMA,GAS)添加到Phase.Transition的列表中。程序会自行处理所有其他的事情,你几乎没有机会出错。从内部来看,Map的Map被实现成了数组的数组,因此在提升了清楚性、安全性和易维护性的同时,在空间或者时间上还几乎不用任何开销。

总而言之,最好不要用序数来索引数组,而要使用EnumMap。如果你所表示的这种关系是多维的,就使用EnumMap<..., EnumMap<...>>。应用程序的程序员在一般情况下都不使用Enum.ordinal,即使要用也很少,因此这是一种特殊情况。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值