1. 程式人生 > >Java程式設計思想 第十九章:列舉型別

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
  1. ordinal方法返回一個int值,這是每個enum例項在宣告時的次序,從0開始。
  2. ==來比較enum例項,編譯器會自動提供equals和hashCode方法。
  3. getDeclaringClass()獲取其所屬的enum類。
  4. name()返回enum例項宣告時的名字,與使用toString()效果相同。
  5. 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());
        }
    }
}

10.1 使用enum的職責鏈

10.2 使用enum的狀態機