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