thinking-in-java(19)枚举类型
1)關(guān)鍵字 enum 可以將一組具名的值的有限集合創(chuàng)建為一種新的類型,而這些具名的值可以作為常規(guī)的程序組件使用;
2)所有的枚舉類都繼承自 Enum,通過 enumClass.getSuperclass() = class java.lang.Enum 得知。??Enum的源碼如下 (本文斗膽把 Enum 稱為 枚舉基類,enum稱為枚舉類,enum中聲明的成員稱為枚舉實(shí)例序列):
// java.lang.Enum枚舉類源碼 public abstract class Enum<E extends Enum<E>>implements Comparable<E>, Serializable {private final String name;public final String name() {return name;}private final int ordinal;public final int ordinal() {return ordinal;}protected Enum(String name, int ordinal) {this.name = name;this.ordinal = ordinal;}public String toString() {return name;}public final boolean equals(Object other) {return this==other;}public final int hashCode() {return super.hashCode();}protected final Object clone() throws CloneNotSupportedException {throw new CloneNotSupportedException();}public final int compareTo(E o) {Enum<?> other = (Enum<?>)o;Enum<E> self = this;if (self.getClass() != other.getClass() && // optimizationself.getDeclaringClass() != other.getDeclaringClass())throw new ClassCastException();return self.ordinal - other.ordinal;}@SuppressWarnings("unchecked")public final Class<E> getDeclaringClass() {Class<?> clazz = getClass();Class<?> zuper = clazz.getSuperclass();return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;}public static <T extends Enum<T>> T valueOf(Class<T> enumType,String name) {T result = enumType.enumConstantDirectory().get(name);if (result != null)return result;if (name == null)throw new NullPointerException("Name is null");throw new IllegalArgumentException("No enum constant " + enumType.getCanonicalName() + "." + name);}/*** enum classes cannot have finalize methods.*/protected final void finalize() { }/*** prevent default deserialization*/private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException {throw new InvalidObjectException("can't deserialize enum");}private void readObjectNoData() throws ObjectStreamException {throw new InvalidObjectException("can't deserialize enum");} } 【19.1】基本enum特性
【荔枝-基本enum特性】
/* 荔枝-基本enum特性 */ enum Shrubbery {GROUND, CRAWLING, HANGING } public class EnumClass {public static void main(String[] args) {// Shrubbery.values() 返回 enum 實(shí)例的數(shù)組(數(shù)組中的元素嚴(yán)格保持在enum中聲明的順序)for (Shrubbery s : Shrubbery.values()) {print("s.ordinal() = " + s.ordinal()); // s.ordinal() 返回enum實(shí)例在聲明時的次序;printnb("s.compareTo(Shrubbery.CRAWLING) = " + s.compareTo(Shrubbery.CRAWLING));printnb("\ns.equals(Shrubbery.CRAWLING) = " + s.equals(Shrubbery.CRAWLING));print("\n(s == Shrubbery.CRAWLING) = " + (s == Shrubbery.CRAWLING));print("s.getDeclaringClass() = " + s.getDeclaringClass());print("s.name() = " + 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("Enum.valueOf(Shrubbery.class, s) = " + shrub);}} } /* s.ordinal() = 0 s.compareTo(Shrubbery.CRAWLING) = -1 s.equals(Shrubbery.CRAWLING) = false (s == Shrubbery.CRAWLING) = false s.getDeclaringClass() = class chapter19.Shrubbery s.name() = GROUND ---------------------- s.ordinal() = 1 s.compareTo(Shrubbery.CRAWLING) = 0 s.equals(Shrubbery.CRAWLING) = true (s == Shrubbery.CRAWLING) = true s.getDeclaringClass() = class chapter19.Shrubbery s.name() = CRAWLING ---------------------- s.ordinal() = 2 s.compareTo(Shrubbery.CRAWLING) = 1 s.equals(Shrubbery.CRAWLING) = false (s == Shrubbery.CRAWLING) = false s.getDeclaringClass() = class chapter19.Shrubbery s.name() = HANGING ---------------------- Enum.valueOf(Shrubbery.class, s) = HANGING Enum.valueOf(Shrubbery.class, s) = CRAWLING Enum.valueOf(Shrubbery.class, s) = GROUND */ 【代碼解說】
解說1)可以使用 == 來比較 enum實(shí)例,編譯器自動提供了 equals() 和 hashCode() 方法;
解說2)Enum類實(shí)現(xiàn)了 Comparable接口,所以具有 compareTO()方法,Enum 還實(shí)現(xiàn)了 Serializable 接口;
解說3)enum實(shí)例的getDeclaringClass() 方法返回 enum實(shí)例所屬的enum類;
解說4)enum實(shí)例的 name()方法返回 實(shí)例聲明時的名字;
解說5)valueOf()方法是Enum類的靜態(tài)方法,返回相應(yīng)的 enum 實(shí)例數(shù)組;(數(shù)組元素按照聲明時的順序排序)
【19.1.1】將靜態(tài)導(dǎo)入用于 enum
1)使用 static import :?將 enum實(shí)例的標(biāo)識符帶入當(dāng)前的命名空間,無需再用enum類型修飾 enum 實(shí)例;
【荔枝-使用靜態(tài)導(dǎo)入引入 枚舉類】
package chapter5;import static chapter5.Spiciness5.*; // 使用靜態(tài)導(dǎo)入public class Burrito {Spiciness5 degree;public Burrito(Spiciness5 degree) {this.degree = degree;}public void describe() {System.out.print("This burrito is ");switch (degree) {case NOT:System.out.println("not spicy at all.");break;case MILD:case MEDIUM:System.out.println("a little hot.");break;case HOT:case FLAMING:default:System.out.println("maybe too hot.");}}public static void main(String[] args) { // Burrito plain = new Burrito(Spiciness5.NOT); // Burrito greenChile = new Burrito(Spiciness5.MEDIUM); // Burrito jalapeno = new Burrito(Spiciness5.HOT);// 不使用 enum 類修飾的語法Burrito plain = new Burrito(NOT);Burrito greenChile = new Burrito(MEDIUM);Burrito jalapeno = new Burrito(HOT);plain.describe();greenChile.describe();jalapeno.describe();} } /* This burrito is not spicy at all. This burrito is a little hot. This burrito is maybe too hot. */ 【荔枝【編譯報錯】-使用靜態(tài)導(dǎo)入的前提條件】
package chapter19;import chapter5.Spiciness5; // 但是可以使用普通導(dǎo)入(無法使用靜態(tài)導(dǎo)入)// 荔枝【編譯報錯】-使用靜態(tài)導(dǎo)入的前提條件(Burrito 與 Spiciness5 在同一個包,) // 這里的荔枝是 Spiciness5 在chapter5 包,而Burrito在chapter19包,所以編譯報錯; //import static chapter5.Spiciness5; public class Burrito {Spiciness5 degree;public Burrito(Spiciness5 degree) {this.degree = degree;}public String toString() {return "Burrito is " + degree;}public static void main(String[] args) {System.out.println(new Burrito(Spiciness5.NOT));System.out.println(new Burrito(Spiciness5.MEDIUM));System.out.println(new Burrito(Spiciness5.HOT));} } 【19.2】向enum中添加新方法
1)enum沒有繼承機(jī)制,除此之外,enum類是一個常規(guī)類,有自己的方法和main方法;
【荔枝-每個枚舉實(shí)例能夠返回自身的描述】
// 荔枝-每個枚舉實(shí)例能夠返回自身的描述 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:// 構(gòu)造函數(shù)必須是包或私有訪問private OzWitch(String description) {this.description = description;}// enum類有自己的方法public String getDescription() {return description;}// enum類有 main() 方法public static void main(String[] args) {for (OzWitch witch : OzWitch.values()) // OzWitch.values() 返回 enum 實(shí)例數(shù)組print(witch + ": " + witch.getDescription());} } /* 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 */ 【代碼解說】解說1)在enum 實(shí)例序列的最后添加一個分號: 如 SOUTH("Good by inference, but missing");
解說2)java要求先定義 enum 實(shí)例,后定義 方法 和 屬性;否則編譯報錯;
解說3)荔枝中,enum類的構(gòu)造方法為private,但不會影響創(chuàng)建 enum實(shí)例(不影響包之外的類調(diào)用該枚舉實(shí)例),因?yàn)橹荒茉?enum 內(nèi)部使用構(gòu)造器創(chuàng)建 enum 實(shí)例;
【19.2.1】覆蓋 enum 的方法
1)通過覆蓋 enum 類的 toString() 方法來實(shí)現(xiàn) 描述enum實(shí)例名字;
【荔枝-覆蓋 enum類的toString() 方法】
// 荔枝-覆蓋 enum類的toString() 方法 public enum SpaceShip {// 在enum 實(shí)例序列的最后添加一個分號:SCOUT, CARGO, TRANSPORT, CRUISER, BATTLESHIP, MOTHERSHIP; // 覆蓋toString() 方法@Overridepublic 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()) { // values 返回 枚舉實(shí)例數(shù)組System.out.println(s);}} } /* Scout Cargo Transport Cruiser Battleship Mothership */ 【19.3】switch語句中的enum:這是enum提供的一項(xiàng)非常便利的功能
1)enum類的ordinal()方法: 獲取該 enum實(shí)例的聲明次序;
2)case語句中不必使用 enum類型修飾 enum實(shí)例;
【荔枝-case語句中不必使用 enum類型修飾 enum實(shí)例】
// 定義一個枚舉類 enum Signal {GREEN, YELLOW, RED, }// 荔枝(使用 enum 構(gòu)建一個小型狀態(tài)機(jī))-case語句中不必使用 enum類型修飾 enum實(shí)例 // 不必使用 enum類型修飾 enum實(shí)例,還可以通過 靜態(tài)導(dǎo)入來引入同一個包下的枚舉類實(shí)現(xiàn) public class TrafficLight {Signal color = Signal.RED;public void change() {switch (color) { // switch 塊 與 enum 實(shí)例的結(jié)合使用case RED: // 不必使用 enum類型修飾 enum實(shí)例,不必用 Signal.REDcolor = Signal.GREEN;break;case GREEN: // 不必使用 enum類型修飾 enum實(shí)例,同理color = Signal.YELLOW;break;case YELLOW: // 不必使用 enum類型修飾 enum實(shí)例,同理color = Signal.RED;break;//注意:這里沒有default 語句 }}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);t.change();}} } /* The traffic light is RED The traffic light is GREEN The traffic light is YELLOW The traffic light is RED The traffic light is GREEN The traffic light is YELLOW The traffic light is RED */ 【代碼解說】解說1)當(dāng) switch 與 enum 實(shí)例結(jié)合使用的時候,編譯器允許沒有 default 語句;
解說2)注釋掉其中一個case語句,編譯器也不會報錯;所以必須確保 switch中的case 覆蓋了所有 enum 枚舉實(shí)例分支;
解說3)case語句中有return 語句:編譯器就會抱怨沒有 default 語句了;
【19.4】values()的神秘之處
1)Enum類并沒有values()方法: 編譯器創(chuàng)建的 enum類對象 都繼承自 Enum類;但是Enum類并沒有 values() 方法;
【荔枝-Enum類沒有定義 values()方法,但Enum實(shí)例有 values()方法,這個values()方法是編譯器加上去的】
// 定義一個枚舉類 enum Explore {HERE, THERE } // 荔枝-Enum類沒有定義 values()方法,但Enum實(shí)例有 values()方法,這個values()方法是編譯器加上去的。 public class Reflection {public static Set<String> analyze(Class<?> enumClass) {print("\n----- enumClass = " + enumClass + " -----");print("Interfaces:");// 通過反射獲取 enumClass對應(yīng)的類實(shí)現(xiàn)的接口列表for (Type t : enumClass.getGenericInterfaces()) // java.lang.reflect.Typeprint("t = " + t);print("enumClass.getSuperclass() = " + enumClass.getSuperclass());Set<String> methods = new TreeSet<String>();// 通過反射獲取 enumClass對應(yīng)的類的方法列表for (Method m : enumClass.getMethods())methods.add(m.getName());print("methods = " + methods);return methods;}public static void main(String[] args) {Set<String> exploreMethods = analyze(Explore.class); // Explore 有 values()方法 Set<String> enumMethods = analyze(Enum.class); // Enum 沒有 values()方法print("enumMethods = " + enumMethods + "\n");print("exploreMethods.containsAll(enumMethods) = "+ exploreMethods.containsAll(enumMethods));exploreMethods.removeAll(enumMethods);print("exploreMethods.removeAll(enumMethods); exploreMethods = " + exploreMethods);// Decompile the code for the enum:OSExecute.command("javap chapter19.Explore"); // 對 chapter19.Explore 進(jìn)行反編譯} } /* ----- enumClass = class chapter19.Explore ----- Interfaces: // 父類 enumClass.getSuperclass() = class java.lang.Enum // enum 實(shí)例的方法列表 methods = [compareTo, equals, getClass, getDeclaringClass, hashCode, name, notify, notifyAll, ordinal, toString, valueOf, values, wait]----- enumClass = class java.lang.Enum ----- Interfaces: t = java.lang.Comparable<E> t = interface java.io.Serializable enumClass.getSuperclass() = class java.lang.Object // Enum 類的方法列表 methods = [compareTo, equals, getClass, getDeclaringClass, hashCode, name, notify, notifyAll, ordinal, toString, valueOf, wait] enumMethods = [compareTo, equals, getClass, getDeclaringClass, hashCode, name, notify, notifyAll, ordinal, toString, valueOf, wait]exploreMethods.containsAll(enumMethods) = true exploreMethods.removeAll(enumMethods); exploreMethods = [values] Compiled from "Reflection.java" // 對 chapter19.Explore 進(jìn)行反編譯 final class chapter19.Explore extends java.lang.Enum<chapter19.Explore> { public static final chapter19.Explore HERE;public static final chapter19.Explore THERE;public static chapter19.Explore[] values();public static chapter19.Explore valueOf(java.lang.String);static {}; } */ 【代碼解說】解說1)enum實(shí)例的values() 方法:是編譯器添加的 static方法;Enum 類中沒有定義 values() 方法;
解說2)enum實(shí)例的 valueOf(java.lang.String) 方法(一個參數(shù)):也是編譯器添加的;覆蓋掉了 Enum.valueOf() 方法了;
解說3)Enum類定義了一個 valueOf() 方法(有兩個參數(shù));
// Enum.valueOf() 源碼(兩個參數(shù)) public static <T extends Enum<T>> T valueOf(Class<T> enumType,String name) {T result = enumType.enumConstantDirectory().get(name);if (result != null)return result;if (name == null)throw new NullPointerException("Name is null");throw new IllegalArgumentException("No enum constant " + enumType.getCanonicalName() + "." + name);} 解說4)enum 實(shí)例被 final修飾:所以enum 無法繼承;
2)如果將 enum 實(shí)例向上轉(zhuǎn)型為 Enum 類型,那么values()方法就不可訪問了;
3)Class.getEnumConstants()方法:?通過該對象取得所有 enum 實(shí)例;
【荔枝-通過 Class.getEnumConstants()方法取得所有 enum 實(shí)例】
enum Search {HITHER, YON } // 荔枝-通過 Class.getEnumConstants()方法取得所有 enum 實(shí)例 public class UpcastEnum {public static void main(String[] args) {Search[] vals = Search.values();Enum e = Search.HITHER; // Upcast-向上轉(zhuǎn)型// e.values(); // Enum 類沒有 values() 方法,編譯報錯:// 通過 Class.getEnumConstants()方法 獲取所有 enum 實(shí)例數(shù)組for (Enum en : e.getClass().getEnumConstants())System.out.println(en);} } /* HITHER YON */ 4)不是枚舉的類也可以調(diào)用 Class.getEnumConstants() 方法:
【荔枝-不是枚舉的類調(diào)用 Class.getEnumConstants() 方法(報空指針異常)】
// 荔枝-不是枚舉的類調(diào)用 Class.getEnumConstants() 方法 public class NonEnum {public static void main(String[] args) {Class<Integer> intClass = Integer.class;try {// Integer.class.getEnumConstants() 報空指針異常for (Object en : intClass.getEnumConstants())System.out.println(en);} catch (Exception e) {System.out.println(e);}} } /** Output: java.lang.NullPointerException*/// :~ 【19.5】實(shí)現(xiàn),而非繼承1)所有 enum 類都繼承自 java.lang.Enum 類;
2)由于不能多繼承,所以只能實(shí)現(xiàn);
【荔枝-所有 enum 類都繼承自 java.lang.Enum 類,只能實(shí)現(xiàn)】
// 荔枝-所有 enum 類都繼承自 java.lang.Enum 類,只能實(shí)現(xiàn) enum CartoonCharacter implements Generator<CartoonCharacter> {SLAPPY, SPANKY, PUNCHY, SILLY, BOUNCY, NUTTY, BOB;private Random rand = new Random(47);public CartoonCharacter next() {return values()[rand.nextInt(values().length)];} }public class EnumImplementation {public static <T> void printNext(Generator<T> rg) { // 這里傳入 Generator,是策略模式System.out.print(rg.next() + ", ");}public static void main(String[] args) {// 選擇任何一個實(shí)例CartoonCharacter cc = CartoonCharacter.BOB;for (int i = 0; i < 10; i++)printNext(cc);} } /* BOB, PUNCHY, BOB, SPANKY, NUTTY, PUNCHY, SLAPPY, NUTTY, NUTTY, SLAPPY, */ 【19.6】隨機(jī)選取1)從enum實(shí)例中隨機(jī)選擇enum實(shí)例;
【荔枝-隨機(jī)選取 enum 實(shí)例】
// 荔枝-隨機(jī)選取 enum 實(shí)例 public class Enums {private static Random rand = new Random(47);// 通過反射 Class.getEnumConstants() 來隨機(jī)選取public static <T extends Enum<T>> T random(Class<T> ec) {// 通過 Class.getEnumConstants()方法取得所有 enum 實(shí)例return random(ec.getEnumConstants());}public static <T> T random(T[] values) {return values[rand.nextInt(values.length)]; // 隨機(jī)選取} } // /:~其中,<T extends Enum<T>> 表示 T 是一個 enum 實(shí)例;將 Class<T> 作為參數(shù)的話,就可以利用 Class對象得到 enum 實(shí)例的數(shù)組了;【測試?yán)笾?隨機(jī)選取 enum 實(shí)例】
// 測試?yán)笾?隨機(jī)選取 enum 實(shí)例 enum Activity {SITTING, LYING, STANDING, HOPPING, RUNNING, DODGING, JUMPING, FALLING, FLYING }public class RandomTest {public static void main(String[] args) {for (int i = 0; i < 20; i++)// 這里必須傳入一個 enum是的 class 引用System.out.print(Enums.random(Activity.class) + " ");} } /* STANDING FLYING RUNNING STANDING RUNNING STANDING LYING DODGING SITTING RUNNING HOPPING HOPPING HOPPING RUNNING STANDING LYING FALLING RUNNING FLYING LYING */ 【19.7】使用接口組織枚舉
1)擴(kuò)展枚舉的需求:有時候希望擴(kuò)展原 enum中的元素,有時回使用子類將一個 enum 中的元素進(jìn)行分組;
2)在接口內(nèi)部,創(chuàng)建實(shí)現(xiàn)該接口的枚舉類,以此來擴(kuò)展原枚舉類:以此將元素進(jìn)行分組,達(dá)到將枚舉元素分類組織的目的;
【荔枝-在接口內(nèi)部,創(chuàng)建實(shí)現(xiàn)該接口的枚舉類,以此來擴(kuò)展原枚舉類】
// 荔枝-在接口內(nèi)部,創(chuàng)建實(shí)現(xiàn)該接口的枚舉類,以此來擴(kuò)展原枚舉類 // 將 Food 進(jìn)行分組;且所有的枚舉類都是 Food 類型 public interface Food {// 在接口內(nèi)部,創(chuàng)建實(shí)現(xiàn)該接口的內(nèi)部枚舉類,以此來擴(kuò)展原枚舉類(注意是內(nèi)部枚舉類)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;} } // /:~【代碼解說】
解說1)實(shí)現(xiàn)接口:?是擴(kuò)展枚舉類的唯一辦法;
解說2)上述實(shí)現(xiàn)Food接口的所有枚舉類:? 都是Food類型,屬于Food類下的多個分組;
【測試?yán)笾?在接口內(nèi)部,創(chuàng)建實(shí)現(xiàn)該接口的枚舉類,以此來擴(kuò)展原枚舉類】
//測試?yán)笾?在接口內(nèi)部,創(chuàng)建實(shí)現(xiàn)該接口的枚舉類,以此來擴(kuò)展原枚舉類 public class TypeOfFood {public static void main(String[] args) {Food food = Appetizer.SALAD;food = MainCourse.LASAGNE;food = Dessert.GELATO;food = Coffee.CAPPUCCINO;} } // /:~ 3)當(dāng)需要與一大堆類型交互時,接口沒有enum枚舉類好用
【荔枝-如何創(chuàng)建一個枚舉的枚舉類】
// 荔枝-創(chuàng)建一個枚舉的枚舉 public enum Course {APPETIZER(Food.Appetizer.class), // 這里調(diào)用的是 Course 的私有構(gòu)造器MAINCOURSE(Food.MainCourse.class), DESSERT(Food.Dessert.class), COFFEE(Food.Coffee.class);private Food[] values;// 通過 Class.getEnumConstants()方法取得所有 enum 實(shí)例private Course(Class<? extends Food> kind) { // 私有構(gòu)造器values = kind.getEnumConstants();}public Food randomSelection() {return Enums.random(values);} } // /:~ 【測試?yán)笾?創(chuàng)建一個枚舉的枚舉】// 測試?yán)笾?創(chuàng)建一個枚舉的枚舉 public class Meal {public static void main(String[] args) {for (int i = 0; i < 5; i++) {for (Course course : Course.values()) {Food food = course.randomSelection();System.out.print(food + ", ");}System.out.println("\n----------------------------------------");}} } /* 獲得枚舉的枚舉值 SPRING_ROLLS, VINDALOO, FRUIT, DECAF_COFFEE, ---------------------------------------- SOUP, VINDALOO, FRUIT, TEA, ---------------------------------------- SALAD, BURRITO, FRUIT, TEA, ---------------------------------------- SALAD, BURRITO, CREME_CARAMEL, LATTE, ---------------------------------------- SOUP, BURRITO, TIRAMISU, ESPRESSO, ---------------------------------------- */ 4)將一個枚舉嵌套在另外一個枚舉內(nèi)部;
【荔枝-將一個枚舉嵌套在另外一個枚舉內(nèi)部】
// 荔枝-將一個枚舉嵌套在另外一個枚舉內(nèi)部; enum SecurityCategory {STOCK(Security.Stock.class), BOND(Security.Bond.class); // 兩個enum 實(shí)例Security[] values;SecurityCategory(Class<? extends Security> kind) {values = kind.getEnumConstants();}// 這個接口看做是枚舉類的集合。被嵌套在 枚舉類 SecurityCategory 的內(nèi)部interface Security {// 通過實(shí)現(xiàn)接口來擴(kuò)展枚舉實(shí)例個數(shù)enum Stock implements Security { // 內(nèi)部枚舉類SHORT, LONG, MARGIN}enum Bond implements Security {MUNICIPAL, JUNK}}public Security randomSelection() { // 隨機(jī)選擇枚舉return Enums.random(values);}public static void main(String[] args) {for (int i = 0; i < 10; i++) {SecurityCategory category = Enums.random(SecurityCategory.class); // 隨機(jī)選擇一個 SecurityCategory 枚舉實(shí)例System.out.println(category + ": " + category.randomSelection()); // 打印隨機(jī)選擇的 枚舉實(shí)例 }} } /* BOND: MUNICIPAL BOND: MUNICIPAL STOCK: MARGIN STOCK: MARGIN BOND: JUNK STOCK: SHORT STOCK: LONG STOCK: LONG BOND: MUNICIPAL BOND: JUNK */ 【代碼解說】解說1)Security接口的作用:?將其所包含的 enum 實(shí)例組成成公共類型;
【第二個荔枝-將一個枚舉嵌套在另外一個枚舉內(nèi)部】 ==== 1712200055
// 第二個荔枝-將一個枚舉嵌套在另外一個枚舉內(nèi)部 public enum Meal2 {// 枚舉實(shí)例序列APPETIZER(Food.Appetizer.class), MAINCOURSE(Food.MainCourse.class), DESSERT(Food.Dessert.class), COFFEE(Food.Coffee.class);private Food[] values;private Meal2(Class<? extends Food> kind) {values = kind.getEnumConstants();}public interface Food {enum Appetizer implements Food { // 枚舉類實(shí)現(xiàn)接口(內(nèi)部枚舉類)SALAD, SOUP, SPRING_ROLLS;}enum MainCourse implements Food { // 枚舉類實(shí)現(xiàn)接口(內(nèi)部枚舉類)LASAGNE, BURRITO, PAD_THAI, LENTILS, HUMMOUS, VINDALOO;}enum Dessert implements Food { // 枚舉類實(shí)現(xiàn)接口(內(nèi)部枚舉類)TIRAMISU, GELATO, BLACK_FOREST_CAKE, FRUIT, CREME_CARAMEL;}enum Coffee implements Food { // 枚舉類實(shí)現(xiàn)接口(內(nèi)部枚舉類)BLACK_COFFEE, DECAF_COFFEE, ESPRESSO, LATTE, CAPPUCCINO, TEA, HERB_TEA;}}public Food randomSelection() {return Enums.random(values);}public static void main(String[] args) {for (int i = 0; i < 5; i++) {for (Meal2 meal : Meal2.values()) {Food food = meal.randomSelection();System.out.print(food + ", ");}System.out.println("\n---------------------------------------------");}} } /* SPRING_ROLLS, VINDALOO, FRUIT, DECAF_COFFEE, --------------------------------------------- SOUP, VINDALOO, FRUIT, TEA, --------------------------------------------- SALAD, BURRITO, FRUIT, TEA, --------------------------------------------- SALAD, BURRITO, CREME_CARAMEL, LATTE, --------------------------------------------- SOUP, BURRITO, TIRAMISU, ESPRESSO, --------------------------------------------- */ 【19.8】 使用 EnumSet 替代標(biāo)志
1)枚舉類:要求所有枚舉實(shí)例 都是唯一的,而Set保持不重復(fù)對象,所以enum類和 Set 很相像;但 enum中無法刪除和添加 枚舉實(shí)例;
2)EnumSet:java se5 引入的替代品,替代傳統(tǒng) 的基于 int 的位標(biāo)志;其優(yōu)點(diǎn)是,判斷一個二進(jìn)制位是否存在時,具有更好的表達(dá)能力,無需擔(dān)心性能;
3)EnumSet是一種容器:其元素必須來自同一個 枚舉類的實(shí)例序列;
// java.util.EnumSet 源碼 public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E>implements Cloneable, java.io.Serializable 【荔枝-EnumSet 操作枚舉實(shí)例】
// 荔枝-EnumSet 操作枚舉實(shí)例 package chapter19;//: enumerated/EnumSets.java //Operations on EnumSets import java.util.*; import static net.mindview.util.Print.*; // 這里還采用了靜態(tài)導(dǎo)入,這樣使得枚舉實(shí)例無需枚舉基類Enum修飾;前提是 枚舉類AlarmPoints 與 使用枚舉實(shí)例的類 EnumSets 在同一個包下; import static chapter19.AlarmPoints.*; public class EnumSets {public static void main(String[] args) {EnumSet<AlarmPoints> points = EnumSet.noneOf(AlarmPoints.class); // EnumSet.add(BATHROOM) 把 BATHROOM 枚舉實(shí)例 添加到 points EnumSet 容器points.add(BATHROOM);print("points.add(BATHROOM); points = " + points);// EnumSet.of(STAIR1, STAIR2, KITCHEN) 返回一個存儲了 start1,start2,start3的新EnumSetpoints.addAll(EnumSet.of(STAIR1, STAIR2, KITCHEN));print("points.addAll(EnumSet.of(STAIR1, STAIR2, KITCHEN)); points = " + points);// EnumSet.allOf(AlarmPoints.class) 返回 包含 AlarmPoints類所有的枚舉實(shí)例的新EnumSetpoints = EnumSet.allOf(AlarmPoints.class);points.removeAll(EnumSet.of(STAIR1, STAIR2, KITCHEN));print("points = EnumSet.allOf(AlarmPoints.class); "+ "points.removeAll(EnumSet.of(STAIR1, STAIR2, KITCHEN)); points = " + points);// EnumSet.range(OFFICE1, OFFICE4) 返回 包含 OFFICE1~OFFICE4(包括端點(diǎn))的枚舉實(shí)例的新EnumSetpoints.removeAll(EnumSet.range(OFFICE1, OFFICE4));print("points.removeAll(EnumSet.range(OFFICE1, OFFICE4)); points = " + points);// EnumSet.complementOf(points) 返回 points枚舉實(shí)例集合 相對于 AlarmPoints枚舉實(shí)例集合的的【補(bǔ)集】points = EnumSet.complementOf(points);print("points = EnumSet.complementOf(points); points = " + points);} } /* points.add(BATHROOM); points = [BATHROOM] points.addAll(EnumSet.of(STAIR1, STAIR2, KITCHEN)); points = [STAIR1, STAIR2, BATHROOM, KITCHEN] points = EnumSet.allOf(AlarmPoints.class); points.removeAll(EnumSet.of(STAIR1, STAIR2, KITCHEN)); points = [LOBBY, OFFICE1, OFFICE2, OFFICE3, OFFICE4, BATHROOM, UTILITY] points.removeAll(EnumSet.range(OFFICE1, OFFICE4)); points = [LOBBY, BATHROOM, UTILITY] points = EnumSet.complementOf(points); points = [STAIR1, STAIR2, OFFICE1, OFFICE2, OFFICE3, OFFICE4, KITCHEN] */ 【代碼解說】 EnumSet 方法列表如下:
EnumSet.add(BATHROOM)? 把 BATHROOM 枚舉實(shí)例 添加到 points EnumSet 容器
EnumSet.of(STAIR1, STAIR2, KITCHEN)? 返回一個存儲了 start1,start2,start3的新EnumSet
EnumSet.of(T... array)? EnumSet.f() 方法有很多重載版本;
EnumSet.allOf(AlarmPoints.class)??返回 包含 AlarmPoints類所有的枚舉實(shí)例的新EnumSet
EnumSet.range(OFFICE1, OFFICE4)? 返回 包含 OFFICE1~OFFICE4(包括端點(diǎn))的枚舉實(shí)例的新EnumSet
EnumSet.complementOf(points)??返回 points枚舉實(shí)例集合 相對于 AlarmPoints枚舉實(shí)例集合的的【補(bǔ)集】
4)EnumSet是基于long類型的,long有64位,而一個enum實(shí)例只需要一位 bit 表示其是否存在。?即EnumSet 最多可以存儲 不多于64個 enum 實(shí)例。
5)如果EnumSet 存儲的元素超過64個,怎么辦?
【荔枝-如果EnumSet 存儲的元素超過64個】
// 荔枝-如果EnumSet 存儲的元素超過64個,EnumSet還是可以存儲超過64個的 enum 枚舉實(shí)例 public class BigEnumSet {enum Big { A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10,A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21,A22, A23, A24, A25, A26, A27, A28, A29, A30, A31, A32,A33, A34, A35, A36, A37, A38, A39, A40, A41, A42, A43,A44, A45, A46, A47, A48, A49, A50, A51, A52, A53, A54,A55, A56, A57, A58, A59, A60, A61, A62, A63, A64, A65,A66, A67, A68, A69, A70, A71, A72, A73, A74, A75 }public static void main(String[] args) {EnumSet<Big> bigEnumSet = EnumSet.allOf(Big.class);System.out.println(bigEnumSet);} } /* Output: [A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22, A23, A24, A25, A26, A27, A28, A29, A30, A31, A32, A33, A34, A35, A36, A37, A38, A39, A40, A41, A42, A43, A44, A45, A46, A47, A48, A49, A50, A51, A52, A53, A54, A55, A56, A57, A58, A59, A60, A61, A62, A63, A64, A65, A66, A67, A68, A69, A70, A71, A72, A73, A74, A75] *///:~ 【19.9】使用 EnumMap1)EnumMap:?要求鍵必須來自同一個 enum類的枚舉實(shí)例,EnumMap在內(nèi)部可以有數(shù)組實(shí)現(xiàn);
// java.util.EnumMap 源碼 public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V>implements java.io.Serializable, Cloneable 【荔枝-EnumMap存儲和操作枚舉實(shí)例(這里采用了命令設(shè)計模式)】
// 命令接口 interface Command {void action(); } // 荔枝-EnumMap存儲和操作枚舉實(shí)例(這里采用了命令設(shè)計模式) public class EnumMaps {public static void main(String[] args) {EnumMap<AlarmPoints, Command> em = new EnumMap<AlarmPoints, Command>(AlarmPoints.class);em.put(KITCHEN, new Command() { // 匿名內(nèi)部類public void action() {print("Kitchen fire!");}});em.put(BATHROOM, new Command() { // 匿名內(nèi)部類public void action() {print("Bathroom alert!");}});// 遍歷 EnumMapfor (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(); // 如果get()方法中的鍵值不存在,則報空指針異常} catch (Exception e) {print(e);}} } /* BATHROOM: Bathroom alert! KITCHEN: Kitchen fire! java.lang.NullPointerException */ 【代碼解說】
解說1) 與 EnumSet一樣: enum實(shí)例定義時的順序決定了該enum實(shí)例在 EnumMap? 中的存儲順序;
解說2) EnumMap的優(yōu)點(diǎn): 與常量相比, 常量在編譯期就被固定了,是不能修改的;即使EnumMap的鍵無法修改但允許修改值對象(EnumMap的鍵固定而值可變);
解說3)多路分發(fā):?可以利用 EnumMap 實(shí)現(xiàn)多路分發(fā);
【19.10】常量相關(guān)的方法
1)java 枚舉類機(jī)制:?允許為 每個 enum 實(shí)例編寫不同的抽象方法實(shí)現(xiàn),從而賦予每個 enum 實(shí)例不同的行為;
2)如何實(shí)現(xiàn)常量相關(guān)的方法:?需要為 enum 定義一個或多個 abstract方法,然后為每個 enum 實(shí)例實(shí)現(xiàn)該抽象方法;
【荔枝-實(shí)現(xiàn)常量相關(guān)的方法(通過枚舉類間接實(shí)現(xiàn)多態(tài))】
// 荔枝-為每個 enum 實(shí)例編寫不同的抽象方法實(shí)現(xiàn)(實(shí)現(xiàn)的方法,就是常量相關(guān)的方法,因?yàn)槊杜e類中的實(shí)例是 final 類型,這可以通過 javap 反編譯 enum類來實(shí)現(xiàn)) public enum ConstantSpecificMethod {// 為 每個 enum 實(shí)例編寫不同的抽象方法實(shí)現(xiàn)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) {// enum實(shí)例的 values() 和 valueof()方法是 編譯器為 enum 實(shí)例添加的for (ConstantSpecificMethod csm : values())System.out.println(csm.getInfo());} } /*2017-12-21 .;D:\java\jdk1.8.0_91\lib\tools.jar;D:\java\jdk1.8.0_91\lib\dt.jar; 1.8.0_91 */ 【注意】?請比較 ConstantSpecificMethod 與 命令設(shè)計模式 EnumMaps 類的相似之處;3)無法將 enum 實(shí)例 作為一個類型來使用
【荔枝-無法將 enum 實(shí)例 作為一個類型來使用】
// 荔枝-無法將 enum 實(shí)例 作為一個類型來使用 enum LikeClasses {WINKEN {void behavior() {print("Behavior1");}},BLINKEN {void behavior() {print("Behavior2");}},NOD {void behavior() {print("Behavior3");}};abstract void behavior(); }public class NotClasses {// 無法使用 LikeClasses.WINKEN 來聲明對象引用 // void f1(LikeClasses.WINKEN instance) {} // Nope } /* 反編譯結(jié)果如下: E:\bench-cluster\spring_in_action_eclipse\AThinkingInJava\src>javac -encoding utf-8 chapter19/NotClasses.javaE:\bench-cluster\spring_in_action_eclipse\AThinkingInJava\src>javap chapter19.NotClasses Compiled from "NotClasses.java" public class chapter19.NotClasses {public chapter19.NotClasses(); }E:\bench-cluster\spring_in_action_eclipse\AThinkingInJava\src>javap chapter19.LikeClasses Compiled from "NotClasses.java" abstract class chapter19.LikeClasses extends java.lang.Enum<chapter19.LikeClasses> {public static final chapter19.LikeClasses WINKEN;public static final chapter19.LikeClasses BLINKEN;public static final chapter19.LikeClasses NOD;public static chapter19.LikeClasses[] values();public static chapter19.LikeClasses valueOf(java.lang.String);abstract void behavior();chapter19.LikeClasses(java.lang.String, int, chapter19.LikeClasses$1);static {}; } */ 【代碼解說】解說1)通過反編譯的結(jié)果知:? 每個enum元素都是 LikeClasses 類型的 static final 實(shí)例;
解說2)因?yàn)樗鼈兪莝tatic實(shí)例,所以無法訪問外部類的 非static 元素或方法:?所以對于內(nèi)部的enum 實(shí)例來說,其行為與一般的內(nèi)部類并不相同;
【荔枝-將一個常量相關(guān)的方法關(guān)聯(lián)到選擇上,再使用 EnumSet保存客戶選擇】
// 荔枝-將一個常量相關(guān)的方法關(guān)聯(lián)到選擇上,再使用 EnumSet保存客戶選擇 public class CarWash {public enum Cycle { // enum 類UNDERBODY { // 每個enum實(shí)例編寫不同的抽象方法實(shí)現(xiàn)// 常量相關(guān)的方法,因?yàn)?UNDERBODY 的類型是 public static final chapter19.CarWash$Cycle UNDERBODY; (static final 是常量類)void action() { print("Spraying the underbody");}},WHEELWASH {void action() { // 常量相關(guān)的方法print("Washing the wheels");}},PREWASH {void action() { print("Loosening the dirt");}},BASIC {void action() { // 常量相關(guān)的方法print("The basic wash");}},HOTWAX {void action() { // 常量相關(guān)的方法print("Applying hot wax");}},RINSE {void action() { // 常量相關(guān)的方法print("Rinsing");}},BLOWDRY {void action() { // 常量相關(guān)的方法print("Blowing dry");}};abstract void action();}// EnumSet.of() 有很多重載方法,可以傳入多個 enum實(shí)例進(jìn)行存儲EnumSet<Cycle> cycles = EnumSet.of(Cycle.BASIC, Cycle.RINSE);public void add(Cycle cycle) {cycles.add(cycle);}// 遍歷 EnumSet 中的 enum 實(shí)例public void washCar() {for (Cycle c : cycles) c.action();}public String toString() {return cycles.toString();}public static void main(String[] args) {CarWash wash = new CarWash();print("wash = " + wash);print("\nwash.washCar() = ");wash.washCar(); // 遍歷 EnumSet 中的 enum 實(shí)例// 元素被添加的順序 并不決定其 存儲順序wash.add(Cycle.BLOWDRY);wash.add(Cycle.BLOWDRY); // 重復(fù)元素被忽略wash.add(Cycle.RINSE);wash.add(Cycle.HOTWAX);print("wash = " + wash);print("\nwash.washCar() = ");wash.washCar();} } /* wash = [BASIC, RINSE]wash.washCar() = The basic wash Rinsing wash = [BASIC, HOTWAX, RINSE, BLOWDRY]wash.washCar() = The basic wash Applying hot wax Rinsing Blowing dry */ /* 反編譯結(jié)果: E:\bench-cluster\spring_in_action_eclipse\AThinkingInJava\src>javac -encoding utf-8 chapter19/CarWash.javaE:\bench-cluster\spring_in_action_eclipse\AThinkingInJava\src>javap chapter19.CarWash$Cycle Compiled from "CarWash.java" public abstract class chapter19.CarWash$Cycle extends java.lang.Enum<chapter19.CarWash$Cycle> {public static final chapter19.CarWash$Cycle UNDERBODY;public static final chapter19.CarWash$Cycle WHEELWASH;public static final chapter19.CarWash$Cycle PREWASH;public static final chapter19.CarWash$Cycle BASIC;public static final chapter19.CarWash$Cycle HOTWAX;public static final chapter19.CarWash$Cycle RINSE;public static final chapter19.CarWash$Cycle BLOWDRY;public static chapter19.CarWash$Cycle[] values();public static chapter19.CarWash$Cycle valueOf(java.lang.String);abstract void action();chapter19.CarWash$Cycle(java.lang.String, int, chapter19.CarWash$1);static {}; } */ 【代碼解說】
解說1)與使用匿名內(nèi)部類相比較,定義常量相關(guān)的方法的語法更高效和簡潔;
解說2)EnumSet 不存儲重復(fù)的 enum 實(shí)例,所以對同一個 enum實(shí)例調(diào)用兩次 add() 方法,大于等于第2次的add()方法調(diào)用都會被忽略;
解說3)向EnumSet 添加元素的順序并不能決定其在 EnumSet中的存儲順序,其存儲順序 決定于 enum 實(shí)例定義時的順序;
4)覆蓋 enum實(shí)例的常量相關(guān)的方法;因?yàn)?enum實(shí)例的類型是 static final enum類型,靜態(tài)常量枚舉類型;
【荔枝-覆蓋 enum實(shí)例的常量相關(guān)的方法】
// 荔枝-覆蓋 enum實(shí)例的常量相關(guān)的方法 public enum OverrideConstantSpecific {NUT, // 默認(rèn)方法BOLT, // 默認(rèn)方法 WASHER { // 重載方法// 這是赤裸裸的覆蓋 常量相關(guān)的方法啊。@Overridevoid f() {print("Overridden method");}};void f() {print("default behavior");}public static void main(String[] args) {for (OverrideConstantSpecific ocs : values()) {printnb(ocs + ": ");ocs.f();}} } /* NUT: default behavior BOLT: default behavior WASHER: Overridden method */ 【19.10.1】基于enum的職責(zé)鏈(職責(zé)鏈設(shè)計模式)1)職責(zé)鏈設(shè)計模式:當(dāng)一個請求到來時,它遍歷這個鏈,直到鏈中的某個解決方法能夠處理該請求;
【荔枝-使用enum實(shí)現(xiàn)職責(zé)鏈】
/* 荔枝-使用enum實(shí)現(xiàn)職責(zé)鏈 */ class Mail {// The NO's lower the probability of random selection:enum GeneralDelivery {YES,NO1,NO2,NO3,NO4,NO5} // 枚舉實(shí)例序列enum Scannability {UNSCANNABLE,YES1,YES2,YES3,YES4} // 枚舉實(shí)例序列enum Readability {ILLEGIBLE,YES1,YES2,YES3,YES4} // 枚舉實(shí)例序列enum Address {INCORRECT,OK1,OK2,OK3,OK4,OK5,OK6} // 枚舉實(shí)例序列enum ReturnAddress {MISSING,OK1,OK2,OK3,OK4,OK5} // 枚舉實(shí)例序列GeneralDelivery generalDelivery;Scannability scannability;Readability readability;Address address;ReturnAddress returnAddress;static long counter = 0;long id = counter++;public String toString() { return "Mail " + id; }public String details() {return toString() +", General Delivery: " + generalDelivery +", Address Scanability: " + scannability +", Address Readability: " + readability +", Address Address: " + address +", Return address: " + returnAddress;}// 產(chǎn)生一個包含隨機(jī)enum實(shí)例的Mail對象public static Mail randomMail() {Mail m = new Mail();m.generalDelivery= Enums.random(GeneralDelivery.class);m.scannability = Enums.random(Scannability.class);m.readability = Enums.random(Readability.class);m.address = Enums.random(Address.class);m.returnAddress = Enums.random(ReturnAddress.class);return m;}// 遍歷 Mail 對象的迭代器(遍歷下一個元素,通過調(diào)用randomMail() 來實(shí)現(xiàn)。 )public static Iterable<Mail> generator(final int count) {return new Iterable<Mail>() { // 匿名內(nèi)部類int n = count;public Iterator<Mail> iterator() {return new Iterator<Mail>() {public boolean hasNext() { return n-- > 0; }public Mail next() { return randomMail(); } // 產(chǎn)生一個包含隨機(jī)enum實(shí)例的Mail對象public void remove() { // Not implementedthrow new UnsupportedOperationException();}};}};} } // PostOffice類內(nèi)部 封裝了多個枚舉類 public class PostOffice {enum MailHandler {GENERAL_DELIVERY {@Overrideboolean handle(Mail m) { // 3.這是重寫 enum 常量相關(guān)的方法switch(m.generalDelivery) {case YES:print("Using general delivery for " + m);return true;default: return false;}}},MACHINE_SCAN {@Overrideboolean handle(Mail m) { // 3.這是重寫 enum 常量相關(guān)的方法switch(m.scannability) {case UNSCANNABLE: return false;default:switch(m.address) {case INCORRECT: return false;default:print("Delivering "+ m + " automatically");return true;}}}},VISUAL_INSPECTION {@Overrideboolean handle(Mail m) { // 3.這是重寫 enum 常量相關(guān)的方法switch(m.readability) {case ILLEGIBLE: return false;default:switch(m.address) {case INCORRECT: return false;default:print("Delivering " + m + " normally");return true;}}}},RETURN_TO_SENDER {@Overrideboolean handle(Mail m) { // 3.這是重寫 enum 常量相關(guān)的方法switch(m.returnAddress) {case MISSING: return false;default:print("Returning " + m + " to sender");return true;}}};abstract boolean handle(Mail m); // 抽象方法}// 2.靜態(tài)方法static void handle(Mail m) {for(MailHandler handler : MailHandler.values())// 調(diào)用被覆蓋的常量相關(guān)的方法,如果能夠處理(返回true),則停止遍歷職責(zé)鏈if(handler.handle(m)) return;print(m + " is a dead letter");}public static void main(String[] args) {for(Mail mail : Mail.generator(10)) {print(mail.details()); // 調(diào)用 Mail.toString() 方法handle(mail); // 1.靜態(tài)方法print("*****");}} } /* Mail 0, General Delivery: NO2, Address Scanability: UNSCANNABLE, Address Readability: YES3, Address Address: OK1, Return address: OK1 Delivering Mail 0 normally ***** Mail 1, General Delivery: NO5, Address Scanability: YES3, Address Readability: ILLEGIBLE, Address Address: OK5, Return address: OK1 Delivering Mail 1 automatically ***** Mail 2, General Delivery: YES, Address Scanability: YES3, Address Readability: YES1, Address Address: OK1, Return address: OK5 Using general delivery for Mail 2 ***** Mail 3, General Delivery: NO4, Address Scanability: YES3, Address Readability: YES1, Address Address: INCORRECT, Return address: OK4 Returning Mail 3 to sender ***** Mail 4, General Delivery: NO4, Address Scanability: UNSCANNABLE, Address Readability: YES1, Address Address: INCORRECT, Return address: OK2 Returning Mail 4 to sender ***** Mail 5, General Delivery: NO3, Address Scanability: YES1, Address Readability: ILLEGIBLE, Address Address: OK4, Return address: OK2 Delivering Mail 5 automatically ***** Mail 6, General Delivery: YES, Address Scanability: YES4, Address Readability: ILLEGIBLE, Address Address: OK4, Return address: OK4 Using general delivery for Mail 6 ***** Mail 7, General Delivery: YES, Address Scanability: YES3, Address Readability: YES4, Address Address: OK2, Return address: MISSING Using general delivery for Mail 7 ***** Mail 8, General Delivery: NO3, Address Scanability: YES1, Address Readability: YES3, Address Address: INCORRECT, Return address: MISSING Mail 8 is a dead letter // Mail 8 對象是 職責(zé)鏈無法解決的。 ***** Mail 9, General Delivery: NO1, Address Scanability: UNSCANNABLE, Address Readability: YES2, Address Address: OK1, Return address: OK4 Delivering Mail 9 normally ***** */ 【代碼解說】解說1)以上職責(zé)鏈由 enum MailHandler實(shí)現(xiàn),而 enum定義的枚舉實(shí)例次序決定了各個策略在應(yīng)用時的次序;
【19.10.2】使用enum的狀態(tài)機(jī)(狀態(tài)設(shè)計模式:如成都各大高校中的米源自動售貨機(jī))
1)枚舉類型非常適合用來創(chuàng)建狀態(tài)機(jī):?一個狀態(tài)機(jī)有有限個狀態(tài)。?狀態(tài)機(jī)通常根據(jù)輸入,從一個狀態(tài)轉(zhuǎn)移到下一個狀態(tài)。也有瞬時狀態(tài),一旦任務(wù)結(jié)束,狀態(tài)機(jī)就離開瞬時狀態(tài);
【狀態(tài)機(jī)荔枝-自動售貨機(jī)】
// 狀態(tài)機(jī)荔枝-自動售貨機(jī)(枚舉類和枚舉實(shí)例) public enum Input {NICKEL(5), DIME(10), QUARTER(25), DOLLAR(100),TOOTHPASTE(200), CHIPS(75), SODA(100), SOAP(50),ABORT_TRANSACTION {public int amount() { // 重寫常量相關(guān)的方法throw new RuntimeException("ABORT.amount()");}},STOP { public int amount() { // 重寫常量相關(guān)的方法throw new RuntimeException("SHUT_DOWN.amount()");}}; int value; // In centsInput(int value) { this.value = value; }Input() {}int amount() { return value; }; // In centsstatic Random rand = new Random(47);public static Input randomSelection() {// Don't include STOP:return values()[rand.nextInt(values().length - 1)];} } ///:~ // 荔枝-自動販賣機(jī)開始 package chapter19; import java.io.File; import java.util.*;import net.mindview.util.*; import static chapter19.Input.*; // 靜態(tài)導(dǎo)入,主要枚舉實(shí)例無需枚舉類修飾 import static net.mindview.util.Print.*;enum Category {// 枚舉實(shí)例序列: MONEY是Category的枚舉實(shí)例,而NICKEL等是 Input的枚舉實(shí)例MONEY(NICKEL, DIME, QUARTER, DOLLAR), // 把MONEY NICKEL, DIME, QUARTER, DOLLAR 作為 EnumMap的4個key,而把MONEY 作為值(4個鍵對應(yīng)一個值)插入到EnumMap中ITEM_SELECTION(TOOTHPASTE, CHIPS, SODA, SOAP),QUIT_TRANSACTION(ABORT_TRANSACTION),SHUT_DOWN(STOP);private Input[] values;Category(Input... types) { // 構(gòu)造方法values = types; // 這里的values 就是插入到 EnumMap的values} // 使用 EnumMap 作為枚舉實(shí)例的容器private static EnumMap<Input,Category> categories = new EnumMap<Input,Category>(Input.class);static { // 靜態(tài)塊for(Category c : Category.class.getEnumConstants())for(Input type : c.values)categories.put(type, c); // 把Input枚舉實(shí)例作為鍵 和 把Category枚舉實(shí)例作為值插入到EnumMap中}public static Category categorize(Input input) {return categories.get(input); // 在EnumMap中獲取以input枚舉實(shí)例作為鍵的 Category 值;} } // 荔枝-自動販賣機(jī) public class VendingMachine {public static final String path = System.getProperty("user.dir") + File.separator + "chapter19" + File.separator; private static State state = State.RESTING;private static int amount = 0;private static Input selection = null;enum StateDuration { TRANSIENT } // StateDuration 也是枚舉類,其只有TRANSIENT 這一個枚舉實(shí)例; enum State { // State 枚舉類聲明開始RESTING { // 枚舉實(shí)例,并重寫State枚舉類的next()方法void next(Input input) {// Category.categorize(input): 在EnumMap中獲取以input枚舉實(shí)例作為鍵的 Category 值switch(Category.categorize(input)) {case MONEY:amount += input.amount();state = ADDING_MONEY;break;case SHUT_DOWN:state = TERMINAL; // 停止default:}}}, ADDING_MONEY { // 枚舉實(shí)例,并重寫State枚舉類的next()方法void next(Input input) {switch(Category.categorize(input)) {case MONEY:amount += input.amount();break;case ITEM_SELECTION:selection = input;if(amount < selection.amount())print("Insufficient money for " + selection);else state = DISPENSING;break;case QUIT_TRANSACTION:state = GIVING_CHANGE;break;case SHUT_DOWN:state = TERMINAL; // 停止default:}}}, // 調(diào)用 State 有參構(gòu)造方法DISPENSING(StateDuration.TRANSIENT) { // 枚舉實(shí)例,并重寫State枚舉類的next()方法void next() {print("here is your " + selection);amount -= selection.amount();state = GIVING_CHANGE;}},GIVING_CHANGE(StateDuration.TRANSIENT) { // 枚舉實(shí)例,并重寫State枚舉類的next()方法void next() {if(amount > 0) {print("Your change: " + amount);amount = 0;}state = RESTING;}}, TERMINAL { void output() { print("Halted"); } }; // 枚舉實(shí)例,并重寫State枚舉類的output()方法private boolean isTransient = false; // 是否是瞬時狀態(tài)State() {} // State 無參構(gòu)造方法State(StateDuration trans) { isTransient = true; } // State 有參構(gòu)造方法(只有調(diào)用有參的State構(gòu)造器,才會進(jìn)入瞬時狀態(tài))void next(Input input) { // 枚舉實(shí)例不重寫枚舉類State的 next(Input input) 方法就拋異常throw new RuntimeException("Only call " +"next(Input input) for non-transient states");}void next() { // 枚舉實(shí)例不重寫枚舉類State的 next() 方法就拋異常throw new RuntimeException("Only call next() for " +"StateDuration.TRANSIENT states");}void output() { print(amount); }} // State 枚舉類聲明結(jié)束static void run(Generator<Input> gen) { // VendingMachine 的靜態(tài)方法while(state != State.TERMINAL) {state.next(gen.next()); // gen.next() 返回 Input 枚舉類中名為 input.next()=文件中的下一個字符串 的Input類型的枚舉實(shí)例while(state.isTransient) // 只要state 不是 DISPENSING 或 GIVING_CHANGE 枚舉實(shí)例,state.isTransient始終未falsestate.next();state.output(); // 只有 TERMINAL State枚舉實(shí)例重寫了 output() 方法}}public static void main(String[] args) {Generator<Input> gen = new RandomInputGenerator();args = new String[]{"",""};if(args.length == 1)gen = new FileInputGenerator(path + "VendingMachineInput.txt");run(gen);} } // For a basic sanity check: class RandomInputGenerator implements Generator<Input> {public Input next() { return Input.randomSelection(); } }// Create Inputs from a file of ';'-separated strings: class FileInputGenerator implements Generator<Input> {private Iterator<String> input;public FileInputGenerator(String fileName) { // 構(gòu)造器input = new TextFile(fileName, ";").iterator();}public Input next() { // 隨機(jī)輸入生成器的next()方法if(!input.hasNext())return null;// Enum.valueOf(Input.class, input.next().trim()) 返回 Input 枚舉類中名為 input.next()=文件中的下一個字符串 的Input類型的枚舉實(shí)例return Enum.valueOf(Input.class, input.next().trim());} } /* Output: 也有可能一時半會停不下來,哈哈!! 25 50 75 here is your CHIPS 0 100 200 here is your TOOTHPASTE 0 25 35 Your change: 35 0 25 35 Insufficient money for SODA 35 60 70 75 Insufficient money for SODA 75 Your change: 75 0 Halted *///:~ /* VendingMachineInput.txt 下面是售貨機(jī)的操作類型列表 QUARTER; QUARTER; QUARTER; CHIPS; DOLLAR; DOLLAR; TOOTHPASTE; QUARTER; DIME; ABORT_TRANSACTION; QUARTER; DIME; SODA; QUARTER; DIME; NICKEL; SODA; ABORT_TRANSACTION; STOP; */ 【19.11】多路分發(fā)1)如運(yùn)算表達(dá)式 a.plus(b):?a 和 b的數(shù)據(jù)類型都未知,如何讓a和b參與運(yùn)算呢?即要執(zhí)行的操作包含了不止一個類型未知的對象,這里是有兩個類型未知的對象(a和b),
而java動態(tài)綁定機(jī)制只能處理其中一種類型,這無法解決這個問題。所以必須自定義動態(tài)綁定行為;
2)解決上面的問題是多路分發(fā):?如果要處理多個不同的類型體系,就需要為每個類型體系執(zhí)行一個方法調(diào)用。
【荔枝-使用 enum 實(shí)現(xiàn)二路分發(fā)的荔枝(經(jīng)典荔枝-基于enum的二路分發(fā))】
// Outcome.java 源碼 package chapter19;public enum Outcome { WIN, LOSE, DRAW } // 贏,輸,平局// Competitor.java 源碼 package chapter19; public interface Competitor<T extends Competitor<T>> {Outcome compete(T competitor); } ///:~// RoShamBo1.java 源碼 package chapter19;import java.util.*; import static chapter19.Outcome.*; // 靜態(tài)導(dǎo)入// 荔枝-使用 enum 實(shí)現(xiàn)二路分發(fā)的荔枝 // Item 接口 interface Item {Outcome compete(Item it);Outcome eval(Paper p);Outcome eval(Scissors s);Outcome eval(Rock r); } // Item 接口實(shí)現(xiàn)類 class Paper implements Item {// public enum Outcome { WIN, LOSE, DRAW } // 贏,輸,平局 ( Outcome 枚舉類型實(shí)例)public Outcome compete(Item it) { return it.eval(this); } // 返回 Outcome 枚舉類型實(shí)例public Outcome eval(Paper p) { return DRAW; }public Outcome eval(Scissors s) { return WIN; }public Outcome eval(Rock r) { return LOSE; }public String toString() { return "Paper"; } } //Item 接口實(shí)現(xiàn)類 class Scissors implements Item {public Outcome compete(Item it) { return it.eval(this); } // 返回 Outcome 枚舉類型實(shí)例public Outcome eval(Paper p) { return LOSE; }public Outcome eval(Scissors s) { return DRAW; }public Outcome eval(Rock r) { return WIN; }public String toString() { return "Scissors"; } } //Item 接口實(shí)現(xiàn)類 class Rock implements Item {public Outcome compete(Item it) { return it.eval(this); } // 返回 Outcome 枚舉類型實(shí)例public Outcome eval(Paper p) { return WIN; }public Outcome eval(Scissors s) { return LOSE; }public Outcome eval(Rock r) { return DRAW; }public String toString() { return "Rock"; } } public class RoShamBo1 {static final int SIZE = 20;private static Random rand = new Random(47);public static Item newItem() { // 隨機(jī)創(chuàng)建一個Item的實(shí)現(xiàn)類switch(rand.nextInt(3)) { // rand.nextInt(3) 取隨機(jī)數(shù) default:case 0: return new Scissors();case 1: return new Paper();case 2: return new Rock();}}// 比較大小public static void match(Item a, Item b) {System.out.println(a + " vs. " + b + ": " + a.compete(b)); // 相應(yīng)的Item接口實(shí)現(xiàn)類的 compete()方法}public static void main(String[] args) {for(int i = 0; i < SIZE; i++)match(newItem(), newItem());} } /* Output: Rock vs. Rock: DRAW Paper vs. Rock: WIN Paper vs. Rock: WIN Paper vs. Rock: WIN Scissors vs. Paper: WIN Scissors vs. Scissors: DRAW Scissors vs. Paper: WIN Rock vs. Paper: LOSE Paper vs. Paper: DRAW Rock vs. Paper: LOSE Paper vs. Scissors: LOSE Paper vs. Scissors: LOSE Rock vs. Scissors: WIN Rock vs. Paper: LOSE Paper vs. Rock: WIN Scissors vs. Paper: WIN Paper vs. Scissors: LOSE Paper vs. Scissors: LOSE Paper vs. Scissors: LOSE Paper vs. Scissors: LOSE *///:~ 【代碼解說】
解說1)Item是這幾種方法類型的接口,一共有三種分發(fā)類型,所以你會看到 eval() 方法被重載了9次,因?yàn)?*3,每兩種類型的比較就可以確定一個eval()的重載版本;
解說2)可以看到,在調(diào)用 a.compete(b) 時,a和b的類型都是 Item,但具體是什么類型未知。但是通過 a 和 b的組合就可以知道調(diào)用哪一個 compete() 和 eval() 重載版本的方法;
解說3)多路分發(fā)的好處:在于保持方法調(diào)用時的優(yōu)雅語法,避免了在一個方法中判定多個對象的類型的丑陋代碼;你只需要說:嘿,你們兩個,我不在乎你們是什么類型,請自行交流; (不能再干貨-基于enum和多態(tài)的多路分發(fā)荔枝)
【19.11.1】使用enum實(shí)現(xiàn)多路分發(fā)
1)使用構(gòu)造器來初始化每個 enum 實(shí)例,并以一組結(jié)果作為參數(shù);它們的組合就形成了類似查詢表的結(jié)構(gòu):
【荔枝-使用enum實(shí)現(xiàn)多路分發(fā)】
// 荔枝-使用enum實(shí)現(xiàn)多路分發(fā) public enum RoShamBo2 implements Competitor<RoShamBo2> {// enum實(shí)例序列,調(diào)用RoShamBo2的構(gòu)造器PAPER(DRAW, LOSE, WIN), //DRAW, LOSE, WIN分布賦值給 vPAPER, vSCISSORS, vROCKSCISSORS(WIN, DRAW, LOSE), // 同理ROCK(LOSE, WIN, DRAW); // 同理private Outcome vPAPER, vSCISSORS, vROCK; // Outcome類型的枚舉實(shí)例// RoShamBo2枚舉類的構(gòu)造方法RoShamBo2(Outcome paper,Outcome scissors,Outcome rock) {this.vPAPER = paper; // 給Output實(shí)例賦值this.vSCISSORS = scissors; // 給Output實(shí)例賦值this.vROCK = rock; // 給Output實(shí)例賦值} // 重寫 compete() 比較方法@Overridepublic Outcome compete(RoShamBo2 it) {switch(it) {default:case PAPER: return vPAPER;case SCISSORS: return vSCISSORS;case ROCK: return vROCK;}}public static void main(String[] args) {RoShamBo.play(RoShamBo2.class, 20);} } /* Output: ROCK vs. ROCK: DRAW SCISSORS vs. ROCK: LOSE SCISSORS vs. ROCK: LOSE SCISSORS vs. ROCK: LOSE PAPER vs. SCISSORS: LOSE PAPER vs. PAPER: DRAW PAPER vs. SCISSORS: LOSE ROCK vs. SCISSORS: WIN SCISSORS vs. SCISSORS: DRAW ROCK vs. SCISSORS: WIN SCISSORS vs. PAPER: WIN SCISSORS vs. PAPER: WIN ROCK vs. PAPER: LOSE ROCK vs. SCISSORS: WIN SCISSORS vs. ROCK: LOSE PAPER vs. SCISSORS: LOSE SCISSORS vs. PAPER: WIN SCISSORS vs. PAPER: WIN SCISSORS vs. PAPER: WIN SCISSORS vs. PAPER: WIN *///:~ //RoShamBo.java 源碼 public class RoShamBo {// 注意方法的基于泛型的返回值類型并比較public static <T extends Competitor<T>> void match(T a, T b) {System.out.println(a + " vs. " + b + ": " + a.compete(b));}// 注意方法的基于泛型的返回值類型并比較public static <T extends Enum<T> & Competitor<T>> void play(Class<T> rsbClass, int size) {for(int i = 0; i < size; i++)match(Enums.random(rsbClass),Enums.random(rsbClass));} } ///:~ // Enums.random() 方法源碼 public class Enums {private static Random rand = new Random(47);// 通過反射 Class.getEnumConstants() 來隨機(jī)選取public static <T extends Enum<T>> T random(Class<T> ec) {// 通過 Class.getEnumConstants()方法取得所有 enum 實(shí)例return random(ec.getEnumConstants());}public static <T> T random(T[] values) {return values[rand.nextInt(values.length)]; // 隨機(jī)選取} } // /:~ 【代碼解說】解說1) 在compete() 分發(fā)中,只要a和b的類型確定下來,就可以找到唯一的case,從而返回執(zhí)行結(jié)果;
解說2) match()方法的返回值類型是 <T extends Competitor<T>>;而play()方法的返回值類型是? <T extends Enum<T> & Competitor<T>> 注意是 <T extends Enum<T> 類型且 Competitor<T>類型;
【19.11.2】使用常量相關(guān)的方法實(shí)現(xiàn)多路分發(fā)
1)把enum 用在 switch語句中實(shí)現(xiàn)多路分發(fā),如下:
【荔枝-把enum 用在 switch語句中實(shí)現(xiàn)多路分發(fā)】
// 荔枝-再把RoShamBo3壓縮一下 // 把enum用于switch語句實(shí)現(xiàn)多路分發(fā) public enum RoShamBo4 implements Competitor<RoShamBo4> {ROCK {public Outcome compete(RoShamBo4 opponent) {return compete(SCISSORS, opponent);}},SCISSORS {public Outcome compete(RoShamBo4 opponent) {return compete(PAPER, opponent);}},PAPER {public Outcome compete(RoShamBo4 opponent) {return compete(ROCK, opponent);}};// 執(zhí)行第二次分發(fā)// 該方法執(zhí)行一系列比較,其行為類似于 switch 語句Outcome compete(RoShamBo4 loser, RoShamBo4 opponent) {return ((opponent == this) ? Outcome.DRAW: ((opponent == loser) ? Outcome.WIN: Outcome.LOSE));}public static void main(String[] args) {RoShamBo.play(RoShamBo4.class, 20);} } /* Same output as RoShamBo2.java *///:~ /* PAPER vs. PAPER: DRAW SCISSORS vs. PAPER: WIN SCISSORS vs. PAPER: WIN SCISSORS vs. PAPER: WIN ROCK vs. SCISSORS: WIN ROCK vs. ROCK: DRAW ROCK vs. SCISSORS: WIN PAPER vs. SCISSORS: LOSE SCISSORS vs. SCISSORS: DRAW PAPER vs. SCISSORS: LOSE SCISSORS vs. ROCK: LOSE SCISSORS vs. ROCK: LOSE PAPER vs. ROCK: WIN PAPER vs. SCISSORS: LOSE SCISSORS vs. PAPER: WIN ROCK vs. SCISSORS: WIN SCISSORS vs. ROCK: LOSE SCISSORS vs. ROCK: LOSE SCISSORS vs. ROCK: LOSE SCISSORS vs. ROCK: LOSE */ 【19.11.3】使用 EnumMap 分發(fā)
1)使用 EnumMap能夠?qū)崿F(xiàn) 真正的 兩路分發(fā);
【荔枝-使用 EnumMap能夠?qū)崿F(xiàn) 真正的 兩路分發(fā)】
1)進(jìn)一步簡化實(shí)現(xiàn)兩路分發(fā)的解決方案。(這種方法很可能也是最快速的,因?yàn)镋numMap內(nèi)部是基于數(shù)組實(shí)現(xiàn)的)
【荔枝-使用二維數(shù)組實(shí)現(xiàn)二路分發(fā)】
?1)java中的枚舉比 C 胡 C++ 中的enum 更加成熟;
?2)本章正好說明了 枚舉所能帶來的價值。?有時,恰恰因?yàn)樗?#xff0c;你才可以優(yōu)雅而干凈地解決問題;
?3)java 1.0 對 術(shù)語 enumeration 的選擇正式一個不幸的反例。?對于一個專門用于從序列中選擇每一個元素的對象而言,java 1.0竟然沒有使用 俗語 iterator來表示。()
?4)java 的后序版本修正了這個錯誤。?但是 Enumeration 接口已經(jīng)無法輕易抹去;
總結(jié)
以上是生活随笔為你收集整理的thinking-in-java(19)枚举类型的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电脑只有一个c盘如何分区磁盘电脑c盘如何
- 下一篇: 笔记本电脑怎么一揭盖就自动开机如何电脑自