1. 程式人生 > >用EnumSet代替位域。

用EnumSet代替位域。

如果一個列舉型別的元素主要用在集合中,一般就使用int列舉模式,將2的不同倍數賦予每個常量:

// Bit field enumeration constants - OBSOLETE!

public class Test {

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 SYTLE_ 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就是用單個long來表示,因此它的效能比得上位域的效能。批處理,如removeAll何retainAll,都是利用位演算法來實現的,就像手工替位域實現的那樣。但是可以避免手工位操作時容易出現的錯誤以及不大雅觀的程式碼,因為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<Style>而非EnumSet<Style>,雖然看起來好像所有的客戶端都可以將EnumSet傳到這個方法,但是最好還是接受介面型別而非接受實現型別。這是考慮到可能會有特殊的客戶端要傳遞一些其他的Set實現,並且沒有什麼明顯的缺點。

總而言之,正是因為列舉型別要用在集合(Set)中,所以沒有理由用位域來表示他。EnumSet類集位域的簡潔和效能優勢及列舉型別的所有優點於一身。實際上EnumSet有個缺點,即截止Java 1.6發行版本,他都無法建立不可變的EnumSet,但是這一點很可能在即將出現的版本中得到修正。同時,可以用Collections.unmodifiableSet將EnumSet封裝起來,但是間接性和效能會受到影響。