Effective Java读书笔记十(Java Tips.Day.10)

TIP32 用EnumSet替代位域

看看下一段代码:

    public class Text{
        public static final int STYLE_BOLD = 1<<0;
        public static final int STYLE_ITALY = 1<<1;
        public static final int STYLE_UNDERLINE = 1<<2;
        public static final int STYLE_STRIKETHOUGH = 1<<3;

        public void applyStyles(int style){
            //....
        }

    }

然后你可以用OR这个运算符将几个常量合并到一个集合中,称作位域(bit field)。

    text.applyStyles(STYLE_BOLD | STYLE_ITALY);

然而位域也有其缺点:如果打印一个位域,翻译位域比翻译简单的int枚举常量要苦难的多,要遍历位域表示的所有元素也不太容易。 而且,它貌似也阻止了程序员将这组常量转变为枚举实现,而它的优点,我仅仅能想到它的执行效率会很高。

下面考虑用EnumSet来实现:

    public class Text{
       public enum Style{
           BOLD,ITALIC,UNDERLINE,STRIKETHROUGH;
       }
       public void applyStyles(Set<Style> styles){
            //...
       }
    }

然后,这样来使用它:

    text.applyStyles(EnumSet.of(Style.BOLD,Style.ITALIC,Style.UNDERLINE));

总之,正式因为枚举类型要用在集合中,所以没有必要用位域来表示它。

EnumSet显然拥有位域的简洁和性能优势,同时有枚举类型所有的有点。

当然,它也有缺点,在JAVA 1.6版本时,它都无法创建不可变的EnumSet。或许可以考虑用Collections.unmodifiableSet将EnumSet封装起来,但简洁性和性能会收到影响。

TIP33 用EnumMap替代序数索引

参考31条,你可能会见到利用ordinary方法来索引数组的代码。例如下面这个过于简化的类,用来表示一种烹饪用的香草:

    public static class Herb{
        public enum Type{
            ANNUAL,PERSENAL,BIENNIAL
        }
        private final String name;
        private final Type type;

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

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

现在假设有一个香草的数组,表示一座花园中的植物,你想按照类型(一年生,二年生,多年生)进行组织之后将这些植物列出来:

            Herb[] garden = ...;
            //这里必须有个未受检的警告...具体参考泛型的相关TIPS
            Set<Herb>[] herbsByType = (Set<Herb>[]) new Set[Type.values().length]();
            for (int i=0;i < herbsByType.length;i++){
                herbsByType[i] = new HashSet<>();
            }
            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]);
            }

这段代码希望herbsByType的索引正好对应Herb.Type的ordinary(),而这些工作,都需要程序员本身来维护,使它们正常工作。一旦发生错误,程序就会完成错误的工作,抛出数组越界异常都算是幸运的事情了。

要维护这种对应关系,使用Map是更好的选择。而EnumMap则是一种专门用于枚举键的Map实现。下面是修改后的实现:

            Herb[] garden = ....;
            Map<Herb.Type,Set<Herb>> herbsByType = new EnumMap<Type, Set<Herb>>(Herb.Type.class);
            for (Herb.Type t:Herb.Type.values()) {
                herbsByType.put(t,new HashSet<Herb>());
            }
            for (Herb h:garden){
                herbsByType.get(h.type).add(h);
            }
            System.out.println(herbsByType);

这段程序更简短,清楚,也更安全,性能可以与使用序数的程序媲美。映射关系则被隐藏和封装起来了,无需程序员另外维护。

注意EnumMap的构造方法采用键类型的Class对象:这是一个有限制的类型令牌(bounded type token),它提供了运行时的泛型信息。


总之,最好不要用序数来索引数组,而要使用EnumMap。如果需要维护的关系是多维的,就使用
EnumMap

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值