Java程式設計思想 第十九章:列舉型別
關鍵字enum可以將一組具名的值的有限集合建立為一種新的型別,而這些具名的值可以作為常規的程式元件使用。
1. 基本enum特性
values()返回enum例項的陣列,而且保持宣告的順序:
enum Shrubbery {GROUND, CRAWLING, HANGING}
public class EnumClass {
public static void main(String[] args) {
for (Shrubbery s : Shrubbery.values()) {
print(s + " ordinal: " + s.ordinal());
printnb(s.compareTo(Shrubbery.CRAWLING) + " ");
printnb(s.equals(Shrubbery.CRAWLING) + " ");
print(s == Shrubbery.CRAWLING);
print(s.getDeclaringClass());
print(s.name());
print("----------------------");
}
// Produce an enum value from a string name:
for (String s : "HANGING CRAWLING GROUND".split(" ")) {
Shrubbery shrub = Enum.valueOf(Shrubbery.class, s);
print(shrub);
}
}
}
執行結果:
GROUND ordinal: 0
-1 false false
class Shrubbery
GROUND
-------------- --------
CRAWLING ordinal: 1
0 true true
class Shrubbery
CRAWLING
----------------------
HANGING ordinal: 2
1 false false
class Shrubbery
HANGING
----------------------
HANGING
CRAWLING
GROUND
- ordinal方法返回一個int值,這是每個enum例項在宣告時的次序,從0開始。
- ==來比較enum例項,編譯器會自動提供equals和hashCode方法。
- getDeclaringClass()獲取其所屬的enum類。
- name()返回enum例項宣告時的名字,與使用toString()效果相同。
- valueOf()實在Enum中定義的static方法,他根據給定的名字返回相應的enum例項,如果不存在會丟擲異常。
1.1 將靜態匯入用於enum
使用static import能夠將enum例項的識別符號代入當前的名稱空間,所以無需再用enum型別來修飾enum例項。唯一擔心的是使用靜態匯入會不會導致程式碼令人難以理解。
1.2 向enum中新增新方法
除了不能繼承自一個enum之外,基本上可以將enum看做一個常規類。也就是說,可以新增方法,甚至可以有main方法。
public enum OzWitch {
// Instances must be defined first, before methods:
WEST("Miss Gulch, aka the Wicked Witch of the West"),
NORTH("Glinda, the Good Witch of the North"),
EAST("Wicked Witch of the East, wearer of the Ruby " +
"Slippers, crushed by Dorothy's house"),
SOUTH("Good by inference, but missing");
private String description;
// Constructor must be package or private access:
private OzWitch(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
public static void main(String[] args) {
for (OzWitch witch : OzWitch.values()) {
print(witch + ": " + witch.getDescription());
}
}
}
必須在enum例項序列的最後新增一個分號。Java要求必須先定義enum例項,如果在例項之前定義任何方法或屬性,編譯時會報錯。有意將構造器宣告為private,但對於他的可訪問性並沒有什麼影響,因為即使不宣告為private,我們只能在enum內部使用其構造器建立enum例項。一旦enum的定義結束,編譯器就不允許在使用其構造器來建立任何例項了。
1.3 覆蓋enum的方法
public enum SpaceShip {
SCOUT, CARGO, TRANSPORT, CRUISER, BATTLESHIP, MOTHERSHIP;
@Override
public String toString() {
String id = name();
String lower = id.substring(1).toLowerCase();
return id.charAt(0) + lower;
}
public static void main(String[] args) {
for (SpaceShip s : values()) {
System.out.println(s);
}
}
}
2. switch語句中的enum
一般來說switch中只能使用整形值,而列舉型別天生就具備整形值的次序,並且可以通過ordinal()方法獲取其次序。
enum Signal {
GREEN, YELLOW, RED,
}
public class TrafficLight {
Signal color = Signal.RED;
public void change() {
switch (color) {
// Note that you don't have to say Signal.RED
// in the case statement:
case RED:
color = Signal.GREEN;
break;
case GREEN:
color = Signal.YELLOW;
break;
case YELLOW:
color = Signal.RED;
break;
}
}
@Override
public String toString() {
return "The traffic light is " + color;
}
public static void main(String[] args) {
TrafficLight t = new TrafficLight();
for (int i = 0; i < 7; i++) {
print(t);
t.change();
}
}
}
3.values()的神祕之處
enum類都繼承自Enum類,我們可以檢視Enum中並沒有values()方法。利用反射機制檢視究竟:
values()是由編譯器新增的static方法。同時建立Explore的過程中,編譯器還添加了valueOf()方法。不是Enum類不是已經有valueOf()方法了嗎,不過Enum中的ValueOf()方法需要兩個引數,這個新增方法只需一個引數。由於Set只儲存方法的名字,不考慮簽名,所以removeAll只剩下values。
由於values方法有編譯器插入到enum定義中的static方法,所以enum向上轉型為Enum,那麼values就不可訪問了,不過Class中有一個getEnumConstants方法,所以即便Enum介面中沒有vlaues方法,仍然可以通過Class物件取得所有enum例項:
enum Search {HITHER, YON}
public class UpcastEnum {
public static void main(String[] args) {
Search[] vals = Search.values();
Enum e = Search.HITHER; // Upcast
// e.values(); // No values() in Enum
for (Enum en : e.getClass().getEnumConstants()) {
System.out.println(en);
}
}
} /*
HITHER
YON
*/
getEnumConstants() 獲取所有Enum物件的例項
5. 實現,而非繼承
建立一個新的enum,可以同時實現一個或多個介面
enum CartoonCharacter implements Generator<CartoonCharacter> {
SLAPPY, SPANKY, PUNCHY, SILLY, BOUNCY, NUTTY, BOB;
private Random rand = new Random(47);
@Override
public CartoonCharacter next() {
return values()[rand.nextInt(values().length)];
}
}
public class EnumImplementation {
public static <T> void printNext(Generator<T> rg) {
System.out.print(rg.next() + ", ");
}
public static void main(String[] args) {
// Choose any instance:
CartoonCharacter cc = CartoonCharacter.BOB;
for (int i = 0; i < 10; i++) {
printNext(cc);
}
}
} /*
BOB, PUNCHY, BOB, SPANKY, NUTTY, PUNCHY, SLAPPY, NUTTY, NUTTY, SLAPPY,
*/
6. 隨機選取
7. 使用介面組織列舉
有時希望使用子類將一個enum中的元素進行分組。在一個介面內部建立實現該介面的列舉,以此將元素分組。
public interface Food {
enum Appetizer implements Food {
SALAD, SOUP, SPRING_ROLLS;
}
enum MainCourse implements Food {
LASAGNE, BURRITO, PAD_THAI,
LENTILS, HUMMOUS, VINDALOO;
}
enum Dessert implements Food {
TIRAMISU, GELATO, BLACK_FOREST_CAKE,
FRUIT, CREME_CARAMEL;
}
enum Coffee implements Food {
BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,
LATTE, CAPPUCCINO, TEA, HERB_TEA;
}
}
public class TypeOfFood {
public static void main(String[] args) {
Food food = Appetizer.SALAD;
food = MainCourse.LASAGNE;
food = Dessert.GELATO;
food = Coffee.CAPPUCCINO;
}
}
8. 使用EnumSet替代標誌
Set是一種集合不能新增重複元素。enum也要求其成員是唯一的。
Java SE5引入了EnumSet,是為了通過enum建立一種替代品,以替代傳統的基於int的“位標誌”。這種標誌可以用來表示某種開關資訊,不過,使用這種標誌,最終操作的只是一些bit。使用EnumSet的有點是,它在說明一個二進位制位是否存在時,具有更好的表達能力,並且無需擔心效能。
EnumSet 包含的使用方法:
EnumSet<AlarmPoints> points =EnumSet.noneOf(AlarmPoints.class); // Empty set
points.addAll() 新增所有Enum元素
EnumSet.of() 返回引數中新增的Enum元素集合
points.removeAl() 移除引數中包含的Enum元素集合
EnumSet.complementOf() 用於建立包含與指定的Enum_Set型別相同的元素的EnumSet
研究EnumSet文件,會發現of()方法被過載了很多次,不但為可變數量引數進行了過載,而且為接受2至5個顯式的引數的情況都進行了過載。這也從側面表現了EnumSet對效能的關注。
9. 使用EnumMap
EnumMap要求其中的鍵必須來自於一個enum由於enum本身的限制,所以EnumMap內部是陣列實現。因此EnumMap速度很快,可以放心進行查詢操作。
EnumMaps 包含的方法
EnumMap<AlarmPoints, Command> em = new EnumMap<AlarmPoints, Command>(AlarmPoints.class);
interface Command {
void action();
}
public class EnumMaps {
public static void main(String[] args) {
EnumMap<AlarmPoints, Command> em = new EnumMap<AlarmPoints, Command>(AlarmPoints.class);
em.put(KITCHEN, new Command() {
public void action() {
print("Kitchen fire!");
}
});
em.put(BATHROOM, new Command() {
public void action() {
print("Bathroom alert!");
}
});
for (Map.Entry<AlarmPoints, Command> e : em.entrySet()) {
printnb(e.getKey() + ": ");
e.getValue().action();
}
try { // If there's no value for a particular key:
em.get(UTILITY).action();
} catch (Exception e) {
print(e);
}
}
}
與EnumSet一樣,enum例項定義時的次序決定了其在EnumMap中的順序。
main()方法的最後部分說明,enum的每一個例項作為一鍵,總是存在的。但是,如果沒有為這個鍵呼叫put()方法來存入相應的值的話,其對應的值就是null。
10 常量相關的方法
**在Enum中定義的元素都是Enum類中的各個例項物件。每個Enum元素都是一個Enum型別的staic final型別物件。**Java的enum有一個非常有趣的特性,即它允許程式設計師為enum例項編寫方法,從而為每個enum例項賦予各自不同的行為。要實現常量相關的方法,需要為enum定義一個或多個abstract方法,然後為每個enum例項實現該抽象方法:
public enum ConstantSpecificMethod {
DATE_TIME {
String getInfo() {
return
DateFormat.getDateInstance().format(new Date());
}
},
CLASSPATH {
String getInfo() {
return System.getenv("CLASSPATH");
}
},
VERSION {
String getInfo() {
return System.getProperty("java.version");
}
};
abstract String getInfo();
public static void main(String[] args) {
for (ConstantSpecificMethod csm : values()) {
System.out.println(csm.getInfo());
}
}
}