第三十七条:不要以序号作为索引,使用EnumMap代替

我们有时可能会看到使用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方法。

所有文章无条件开放,顺手点个赞不为过吧!

                                                            

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值