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

来源:互联网 发布:c语言入门到精通光盘 编辑:程序博客网 时间:2024/06/08 14:42

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
原创粉丝点击