Effective Java笔记第五章枚举和注解第四节用EnumMap代替序数索引

本文探讨了在Java中避免使用枚举的ordinal()方法索引数组,转而采用EnumMap来提高代码的安全性和效率。通过示例展示了如何使用EnumMap替换序数索引的数组,以实现更清晰、更安全的映射关系。EnumMap不仅提供了类型安全,还结合了Map的功能和数组的快速访问。此外,文章还展示了如何解决多维度枚举映射的问题,进一步优化了代码结构。
摘要由CSDN通过智能技术生成

Effective Java笔记第五章枚举和注解

第四节用EnumMap代替序数索引

1.我们举个例子:

public class Herb {

    public enum Type {
        ANNUAL, PERENNIAL, BIENNIAL;
    }

    private final String name;
    public final Type type;

    public Herb(String name, Type type) {
        this.name = name;
        this.type = type;
    }

    @Override
    public String toString() {
        return name;
    }

}

我们先用ordinal方法作为索引将Herb类按照Type进行分类。

public static void main(String[] args) {
        //Using ordinal() to index an array - DO NOT DO THIS  使用ordinal()索引数组-不要这样做
        //这是一个放有Herb对象的数组
        Herb[] garden = {new Herb("annul", Herb.Type.ANNUAL),
                new Herb("perennial", Herb.Type.BIENNIAL),
                new Herb("biennial", Herb.Type.PERENNIAL),
                new Herb("annul2", Herb.Type.ANNUAL)};
        Set<Herb>[] herbs = //Indexed by Herb.Type.ordinal()    这是一个放Herb集合的数组,每一个元素都是一个Herb集合
                (Set<Herb>[]) new Set[Herb.Type.values().length];
        for (int i = 0; i < herbs.length; i++) {
            //有几个枚举类型就创建几个集合
            herbs[i] = new HashSet<Herb>();
        }
        for (Herb herb : garden) {
            //根据不同的枚举类型把Herb[]数组中的Herb对象放入不同的Set<herb>集合中
            herbs[herb.type.ordinal()].add(herb);
        }
        for (int i = 0; i < herbs.length; i++) {
            System.out.printf("%s: %s%n", Herb.Type.values()[i], herbs[i]);
        }
    }

这种方法确实可行,但是有许多隐藏的问题。因为数组不能和泛型兼容,程序需要进行未受检的转换,并且不能正确无误的进行编译。因为数组不知道他的索引代表着什么,你必须手工标注这些索引的输出。最严重的问题在于,当你访问一个按照枚举的序数进行索引的数组时,使用正确的int值就是你的职责了,int不能提供枚举的类型安全。

对此,我们有一种更好的方法。上面的代码中数组实际上充当着从枚举到值的映射,有一种非常快速的Map实现专门用于枚举键,称作java.util.EnumMap。下面是用EnumMap改写后的代码:

public static void main(String[] args) {
        //Using ordinal() to index an array - DO NOT DO THIS  使用ordinal()索引数组-不要这样做
        //这是一个放有Herb对象的数组
        Herb[] garden = {new Herb("annul", Herb.Type.ANNUAL),
                new Herb("perennial", Herb.Type.BIENNIAL),
                new Herb("biennial", Herb.Type.PERENNIAL),
                new Herb("annul2", Herb.Type.ANNUAL)};
        //对上面的代码进行改进
        //用枚举作为key,以Herb集合作为value
        Map<Herb.Type,Set<Herb>> herbsByType=new EnumMap<Herb.Type, Set<Herb>>(Herb.Type.class);
        for (Herb.Type value : Herb.Type.values()) {
            herbsByType.put(value,new HashSet<Herb>());
        }
        for (Herb herb : garden) {
            herbsByType.get(herb.type).add(herb);
        }
        System.out.println(herbsByType);
    }

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

2.你还可能见到按照序数进行索引(两次)的数组的数组,该序数表示两个枚举值的映射。下面我们举个例子:

public enum Phase {

    SOLID, LIQUID, GAS;

    public enum Transition {

        MELT, FREEZE, BOIL, CONDENSE, SUBLIME, DEPOSIT;

        private static final Transition[][] TRANSITIONS = {
                {null, MELT, SUBLIME},
                {FREEZE, null, BOIL},
                {DEPOSIT, CONDENSE, null}
        };

        public static Transition from(Phase src, Phase dst) {
            return TRANSITIONS[src.ordinal()][dst.ordinal()];
        }

    }

}
public class PhaseTest {

    public static void main(String[] args) {
        Phase.Transition from = Phase.Transition.from(Phase.GAS, Phase.SOLID);
        System.out.println(from);
    }

}

这段程序可行,但是和之前的例子有着相同的缺陷,编译器无法知道序数和数组索引之间的关系,如果在过渡表中出了错,或者修改Phase或者Phase.Transition时忘记将他更新,程序运行时就会失败。下面我们使用EnumMap进行改编:

public enum PhaseImprove {

    SOLID, LIQUID, GAS;

    public enum Transition {
        MELT(SOLID, LIQUID), FREEZE(LIQUID, SOLID),
        BOIL(LIQUID, GAS), CONDENSE(GAS, LIQUID),
        SUBLTIME(SOLID, GAS), DEPOSIT(GAS, SOLID);

        private final PhaseImprove src;
        private final PhaseImprove dst;

        Transition(PhaseImprove src, PhaseImprove dst) {
            this.src = src;
            this.dst = dst;
        }

        //Initialize the phase transition map
        private static final Map<PhaseImprove,Map<PhaseImprove,Transition>> m=new EnumMap<>(PhaseImprove.class);

        static {
            for (PhaseImprove value : PhaseImprove.values()) {
                m.put(value,new EnumMap<PhaseImprove,Transition>(PhaseImprove.class));
            }
            for (Transition value : Transition.values()) {
                m.get(value.src).put(value.dst,value);
            }
        }
        
        public static Transition from(PhaseImprove src,PhaseImprove dst){
            return m.get(src).get(dst);
        }

    }

}
public class PhaseImproveTest {

    public static void main(String[] args) {
        PhaseImprove.Transition from = PhaseImprove.Transition.from(PhaseImprove.GAS, PhaseImprove.LIQUID);
        System.out.println(from);
    }

}

3.总之,最好不要用序数来索引数组,而要使用EnumMap。如果你所表示的这种关系是多维的,就使用
EnumMap< … , EnumMap< … > >。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值