第32条:用EnumSet代替位域

如果一个枚举类型的元素主要用在集合中,一般就是用int枚举模式(见第30条),将 2 的不同倍数赋予每个常量:

// Bit field enumeration constants - OBSOLETE!
public class Text {
    public static final int STYLE_BOLD          = 1 << 0; // 1
    public static final int STYLE_ITALIC        = 1 << 1; // 2
    public static final int STYLE_UNDERLINE     = 1 << 2; // 4
    public static final int STYLE_STRIKETHROUGH = 1 << 3; // 8

    // parameter is bitwise OR of zero or more STYLE_ constants
    public void applyStyles(int styles) { ... }
}

这种表示法让你用OR位运算将几个常量合并到一个集合中,称作位域(bit field)

text.applyStyles(STYLE_BOLD | STYLE_ITALIC);

位域表示法也允许利用位操作,有效地执行像 union(联合)和 intersection(交集)这样的集合操作。但位域有着int枚举常量的所有缺点,甚至更多。当位域以数字形式打印时,翻译位域比翻译简单的int枚举常量要困难得多。甚至,要遍历位域表示的所有元素也没有很容易的方法。

有些程序员优先使用枚举而非int常量,他们在需要传递多组常量集时,仍然倾向于使用位域。其实没有理由这么做,因为还有更好的替代方法。java.util包提供了EnumSet类来有效地表示从单个枚举类型中提取的多个值的多个集合。这个类实现Set接口,提供了丰富的功能、类型安全性,以及可以从任何其他Set实现中得到的互用性。但是在内部具体的实现上,每个EnumSet内容都表示为位矢量。如果底层的枚举类型有64个或者更少的元素——大多如此——整个EnumSet就是用单个long来表示,因此它的性能比得上位域的性能。批处理,如removeAllretainAll,都是利用位算法来实现的,就像手工替位域实现得那样。但是可以避免手工操作时容易出现的错误以及不太雅观的代码,因为EnumSet替你完成了这项艰巨的工作。

下面是一个范例改成用枚举代替位域后的代码,它更加简短、更加清楚,也更加安全:

// EnumSet - a modern replacement for bit fields
public class Text {
    public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH }

    // Any Set could be passed in, but EnumSet is clearly best
    public void applyStyles(Set<Style> styles) { ... }
}

下面是将EnumSet实例传递给applyStyles方法的客户端代码。EnumSet提供了丰富的静态工厂来轻松创建集合,其中一个如这个代码所示:

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

注意applyStyles方法采用的是Set<Styles>而非EnumSet<Style>。虽然看起来好像所有的客户端都可以将EnumSet传递到这个方法,但是最好还是接受接口类型而非接受实现类型。这是考虑到可能会有特殊的客户端要传递一些其他的Set实现,并且没有什么明显的缺点。

总而言之,正式因为枚举类型要用在集合(Set)中,所以没有利用用位域来表示它EnumSet类集位域的简洁和性能优势及第 30 条中所述的枚举类型的所有优点于一身。实际上EnumSet有个缺点,即截止 Java 1.6 发行版本,它都无法创建不可变的EnumSet,但是这一点很可能在即将出来的版本中得到修正。同时,可以用Collections.unmodifiableSetEnumSet封装起来,但是简洁性和性能会受到影响。

results matching ""

    No results matching ""