日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

thinking-in-java(19)枚举类型

發布時間:2023/12/3 编程问答 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 thinking-in-java(19)枚举类型 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
【0】開場白
1)關鍵字 enum
可以將一組具名的值的有限集合創建為一種新的類型,而這些具名的值可以作為常規的程序組件使用;
2)所有的枚舉類都繼承自 Enum,通過 enumClass.getSuperclass() = class java.lang.Enum 得知。??Enum的源碼如下 (本文斗膽把 Enum 稱為 枚舉基類,enum稱為枚舉類,enum中聲明的成員稱為枚舉實例序列)
// 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 實例的數組(數組中的元素嚴格保持在enum中聲明的順序)for (Shrubbery s : Shrubbery.values()) {print("s.ordinal() = " + s.ordinal()); // s.ordinal() 返回enum實例在聲明時的次序;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實例,編譯器自動提供了 equals() 和 hashCode() 方法;
解說2)Enum類實現了 Comparable接口,所以具有 compareTO()方法,Enum 還實現了 Serializable 接口;
解說3)enum實例的getDeclaringClass() 方法返回 enum實例所屬的enum類;
解說4)enum實例的 name()方法返回 實例聲明時的名字;
解說5)valueOf()方法是Enum類的靜態方法,返回相應的 enum 實例數組;(數組元素按照聲明時的順序排序)


【19.1.1】將靜態導入用于 enum
1)使用 static import :?
將 enum實例的標識符帶入當前的命名空間,無需再用enum類型修飾 enum 實例;
【荔枝-使用靜態導入引入 枚舉類】
package chapter5;import static chapter5.Spiciness5.*; // 使用靜態導入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. */ 【荔枝【編譯報錯】-使用靜態導入的前提條件】
package chapter19;import chapter5.Spiciness5; // 但是可以使用普通導入(無法使用靜態導入)// 荔枝【編譯報錯】-使用靜態導入的前提條件(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沒有繼承機制,除此之外,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;}// enum類有自己的方法public String getDescription() {return description;}// enum類有 main() 方法public static void main(String[] args) {for (OzWitch witch : OzWitch.values()) // OzWitch.values() 返回 enum 實例數組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 實例序列的最后添加一個分號: 如 SOUTH("Good by inference, but missing");
解說2)java要求先定義 enum 實例,后定義 方法 和 屬性;否則編譯報錯;
解說3)荔枝中,enum類的構造方法為private,但不會影響創建 enum實例(不影響包之外的類調用該枚舉實例),因為只能在 enum 內部使用構造器創建 enum 實例;

【19.2.1】覆蓋 enum 的方法
1)通過覆蓋 enum 類的 toString() 方法來實現 描述enum實例名字;
【荔枝-覆蓋 enum類的toString() 方法】

// 荔枝-覆蓋 enum類的toString() 方法 public enum SpaceShip {// 在enum 實例序列的最后添加一個分號: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 返回 枚舉實例數組System.out.println(s);}} } /* Scout Cargo Transport Cruiser Battleship Mothership */ 【19.3】switch語句中的enum:這是enum提供的一項非常便利的功能
1)enum類的ordinal()方法:
獲取該 enum實例的聲明次序;
2)case語句中不必使用 enum類型修飾 enum實例;


【荔枝-case語句中不必使用 enum類型修飾 enum實例】

// 定義一個枚舉類 enum Signal {GREEN, YELLOW, RED, }// 荔枝(使用 enum 構建一個小型狀態機)-case語句中不必使用 enum類型修飾 enum實例 // 不必使用 enum類型修飾 enum實例,還可以通過 靜態導入來引入同一個包下的枚舉類實現 public class TrafficLight {Signal color = Signal.RED;public void change() {switch (color) { // switch 塊 與 enum 實例的結合使用case RED: // 不必使用 enum類型修飾 enum實例,不必用 Signal.REDcolor = Signal.GREEN;break;case GREEN: // 不必使用 enum類型修飾 enum實例,同理color = Signal.YELLOW;break;case YELLOW: // 不必使用 enum類型修飾 enum實例,同理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)
當 switch 與 enum 實例結合使用的時候,編譯器允許沒有 default 語句;
解說2)注釋掉其中一個case語句,編譯器也不會報錯;所以必須確保 switch中的case 覆蓋了所有 enum 枚舉實例分支;
解說3)case語句中有return 語句:編譯器就會抱怨沒有 default 語句了;

【19.4】values()的神秘之處
1)Enum類并沒有values()方法:
編譯器創建的 enum類對象 都繼承自 Enum類;但是Enum類并沒有 values() 方法;


【荔枝-Enum類沒有定義 values()方法,但Enum實例有 values()方法,這個values()方法是編譯器加上去的】

// 定義一個枚舉類 enum Explore {HERE, THERE } // 荔枝-Enum類沒有定義 values()方法,但Enum實例有 values()方法,這個values()方法是編譯器加上去的。 public class Reflection {public static Set<String> analyze(Class<?> enumClass) {print("\n----- enumClass = " + enumClass + " -----");print("Interfaces:");// 通過反射獲取 enumClass對應的類實現的接口列表for (Type t : enumClass.getGenericInterfaces()) // java.lang.reflect.Typeprint("t = " + t);print("enumClass.getSuperclass() = " + enumClass.getSuperclass());Set<String> methods = new TreeSet<String>();// 通過反射獲取 enumClass對應的類的方法列表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 進行反編譯} } /* ----- enumClass = class chapter19.Explore ----- Interfaces: // 父類 enumClass.getSuperclass() = class java.lang.Enum // enum 實例的方法列表 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 進行反編譯 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實例的values() 方法:是編譯器添加的 static方法;Enum 類中沒有定義 values() 方法;
解說2)enum實例的 valueOf(java.lang.String) 方法(一個參數):也是編譯器添加的;覆蓋掉了 Enum.valueOf() 方法了;
解說3)Enum類定義了一個 valueOf() 方法(有兩個參數);
// Enum.valueOf() 源碼(兩個參數) 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 實例被 final修飾:所以enum 無法繼承;


2)如果將 enum 實例向上轉型為 Enum 類型,那么values()方法就不可訪問了;

3)Class.getEnumConstants()方法:?通過該對象取得所有 enum 實例;


【荔枝-通過 Class.getEnumConstants()方法取得所有 enum 實例】

enum Search {HITHER, YON } // 荔枝-通過 Class.getEnumConstants()方法取得所有 enum 實例 public class UpcastEnum {public static void main(String[] args) {Search[] vals = Search.values();Enum e = Search.HITHER; // Upcast-向上轉型// e.values(); // Enum 類沒有 values() 方法,編譯報錯:// 通過 Class.getEnumConstants()方法 獲取所有 enum 實例數組for (Enum en : e.getClass().getEnumConstants())System.out.println(en);} } /* HITHER YON */ 4)不是枚舉的類也可以調用 Class.getEnumConstants() 方法:


【荔枝-不是枚舉的類調用 Class.getEnumConstants() 方法(報空指針異常)】

// 荔枝-不是枚舉的類調用 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】實現,而非繼承
1)所有 enum 類都繼承自 java.lang.Enum 類;

2)由于不能多繼承,所以只能實現;

【荔枝-所有 enum 類都繼承自 java.lang.Enum 類,只能實現】

// 荔枝-所有 enum 類都繼承自 java.lang.Enum 類,只能實現 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) {// 選擇任何一個實例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】隨機選取
1)從enum實例中隨機選擇enum實例;


【荔枝-隨機選取 enum 實例】

// 荔枝-隨機選取 enum 實例 public class Enums {private static Random rand = new Random(47);// 通過反射 Class.getEnumConstants() 來隨機選取public static <T extends Enum<T>> T random(Class<T> ec) {// 通過 Class.getEnumConstants()方法取得所有 enum 實例return random(ec.getEnumConstants());}public static <T> T random(T[] values) {return values[rand.nextInt(values.length)]; // 隨機選取} } // /:~其中,<T extends Enum<T>> 表示 T 是一個 enum 實例;將 Class<T> 作為參數的話,就可以利用 Class對象得到 enum 實例的數組了;

【測試荔枝-隨機選取 enum 實例】
// 測試荔枝-隨機選取 enum 實例 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)擴展枚舉的需求:
有時候希望擴展原 enum中的元素,有時回使用子類將一個 enum 中的元素進行分組;
2)在接口內部,創建實現該接口的枚舉類,以此來擴展原枚舉類:以此將元素進行分組,達到將枚舉元素分類組織的目的;


【荔枝-在接口內部,創建實現該接口的枚舉類,以此來擴展原枚舉類】

// 荔枝-在接口內部,創建實現該接口的枚舉類,以此來擴展原枚舉類 // 將 Food 進行分組;且所有的枚舉類都是 Food 類型 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;} } // /:~
【代碼解說】
解說1)實現接口:?
是擴展枚舉類的唯一辦法;
解說2)上述實現Food接口的所有枚舉類:? 都是Food類型,屬于Food類下的多個分組;

【測試荔枝-在接口內部,創建實現該接口的枚舉類,以此來擴展原枚舉類】
//測試荔枝-在接口內部,創建實現該接口的枚舉類,以此來擴展原枚舉類 public class TypeOfFood {public static void main(String[] args) {Food food = Appetizer.SALAD;food = MainCourse.LASAGNE;food = Dessert.GELATO;food = Coffee.CAPPUCCINO;} } // /:~ 3)當需要與一大堆類型交互時,接口沒有enum枚舉類好用


【荔枝-如何創建一個枚舉的枚舉類】

// 荔枝-創建一個枚舉的枚舉 public enum Course {APPETIZER(Food.Appetizer.class), // 這里調用的是 Course 的私有構造器MAINCOURSE(Food.MainCourse.class), DESSERT(Food.Dessert.class), COFFEE(Food.Coffee.class);private Food[] values;// 通過 Class.getEnumConstants()方法取得所有 enum 實例private Course(Class<? extends Food> kind) { // 私有構造器values = kind.getEnumConstants();}public Food randomSelection() {return Enums.random(values);} } // /:~ 【測試荔枝-創建一個枚舉的枚舉】
// 測試荔枝-創建一個枚舉的枚舉 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)將一個枚舉嵌套在另外一個枚舉內部;


【荔枝-將一個枚舉嵌套在另外一個枚舉內部】

// 荔枝-將一個枚舉嵌套在另外一個枚舉內部; enum SecurityCategory {STOCK(Security.Stock.class), BOND(Security.Bond.class); // 兩個enum 實例Security[] values;SecurityCategory(Class<? extends Security> kind) {values = kind.getEnumConstants();}// 這個接口看做是枚舉類的集合。被嵌套在 枚舉類 SecurityCategory 的內部interface Security {// 通過實現接口來擴展枚舉實例個數enum Stock implements Security { // 內部枚舉類SHORT, LONG, MARGIN}enum Bond implements Security {MUNICIPAL, JUNK}}public Security randomSelection() { // 隨機選擇枚舉return Enums.random(values);}public static void main(String[] args) {for (int i = 0; i < 10; i++) {SecurityCategory category = Enums.random(SecurityCategory.class); // 隨機選擇一個 SecurityCategory 枚舉實例System.out.println(category + ": " + category.randomSelection()); // 打印隨機選擇的 枚舉實例 }} } /* BOND: MUNICIPAL BOND: MUNICIPAL STOCK: MARGIN STOCK: MARGIN BOND: JUNK STOCK: SHORT STOCK: LONG STOCK: LONG BOND: MUNICIPAL BOND: JUNK */ 【代碼解說】
解說1)Security接口的作用:?
將其所包含的 enum 實例組成成公共類型;

【第二個荔枝-將一個枚舉嵌套在另外一個枚舉內部】 ==== 1712200055
// 第二個荔枝-將一個枚舉嵌套在另外一個枚舉內部 public enum Meal2 {// 枚舉實例序列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 { // 枚舉類實現接口(內部枚舉類)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 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 替代標志
1)枚舉類:
要求所有枚舉實例 都是唯一的,而Set保持不重復對象,所以enum類和 Set 很相像;但 enum中無法刪除和添加 枚舉實例;
2)EnumSet:java se5 引入的替代品,替代傳統 的基于 int 的位標志;其優點是,判斷一個二進制位是否存在時,具有更好的表達能力,無需擔心性能;
3)EnumSet是一種容器:其元素必須來自同一個 枚舉類的實例序列;
// java.util.EnumSet 源碼 public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E>implements Cloneable, java.io.Serializable 【荔枝-EnumSet 操作枚舉實例】
// 荔枝-EnumSet 操作枚舉實例 package chapter19;//: enumerated/EnumSets.java //Operations on EnumSets import java.util.*; import static net.mindview.util.Print.*; // 這里還采用了靜態導入,這樣使得枚舉實例無需枚舉基類Enum修飾;前提是 枚舉類AlarmPoints 與 使用枚舉實例的類 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 枚舉實例 添加到 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類所有的枚舉實例的新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(包括端點)的枚舉實例的新EnumSetpoints.removeAll(EnumSet.range(OFFICE1, OFFICE4));print("points.removeAll(EnumSet.range(OFFICE1, OFFICE4)); points = " + points);// EnumSet.complementOf(points) 返回 points枚舉實例集合 相對于 AlarmPoints枚舉實例集合的的【補集】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 枚舉實例 添加到 points EnumSet 容器
EnumSet.of(STAIR1, STAIR2, KITCHEN)? 返回一個存儲了 start1,start2,start3的新EnumSet
EnumSet.of(T... array)? EnumSet.f() 方法有很多重載版本;
EnumSet.allOf(AlarmPoints.class)??返回 包含 AlarmPoints類所有的枚舉實例的新EnumSet
EnumSet.range(OFFICE1, OFFICE4)? 返回 包含 OFFICE1~OFFICE4(包括端點)的枚舉實例的新EnumSet
EnumSet.complementOf(points)??返回 points枚舉實例集合 相對于 AlarmPoints枚舉實例集合的的【補集】

4)EnumSet是基于long類型的,long有64位,而一個enum實例只需要一位 bit 表示其是否存在。?即EnumSet 最多可以存儲 不多于64個 enum 實例。
5)如果EnumSet 存儲的元素超過64個,怎么辦?


【荔枝-如果EnumSet 存儲的元素超過64個】

// 荔枝-如果EnumSet 存儲的元素超過64個,EnumSet還是可以存儲超過64個的 enum 枚舉實例 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】使用 EnumMap
1)EnumMap:?
要求鍵必須來自同一個 enum類的枚舉實例,EnumMap在內部可以有數組實現;
// java.util.EnumMap 源碼 public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V>implements java.io.Serializable, Cloneable 【荔枝-EnumMap存儲和操作枚舉實例(這里采用了命令設計模式)】
// 命令接口 interface Command {void action(); } // 荔枝-EnumMap存儲和操作枚舉實例(這里采用了命令設計模式) 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!");}});// 遍歷 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實例定義時的順序決定了該enum實例在 EnumMap? 中的存儲順序;
解說2) EnumMap的優點: 與常量相比, 常量在編譯期就被固定了,是不能修改的;即使EnumMap的鍵無法修改但允許修改值對象(EnumMap的鍵固定而值可變);
解說3)多路分發:?可以利用 EnumMap 實現多路分發;

【19.10】常量相關的方法
1)java 枚舉類機制:?
允許為 每個 enum 實例編寫不同的抽象方法實現,從而賦予每個 enum 實例不同的行為;
2)如何實現常量相關的方法:?需要為 enum 定義一個或多個 abstract方法,然后為每個 enum 實例實現該抽象方法;


【荔枝-實現常量相關的方法(通過枚舉類間接實現多態)】

// 荔枝-為每個 enum 實例編寫不同的抽象方法實現(實現的方法,就是常量相關的方法,因為枚舉類中的實例是 final 類型,這可以通過 javap 反編譯 enum類來實現) public enum ConstantSpecificMethod {// 為 每個 enum 實例編寫不同的抽象方法實現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實例的 values() 和 valueof()方法是 編譯器為 enum 實例添加的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 與 命令設計模式 EnumMaps 類的相似之處;

3)無法將 enum 實例 作為一個類型來使用


【荔枝-無法將 enum 實例 作為一個類型來使用】

// 荔枝-無法將 enum 實例 作為一個類型來使用 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 } /* 反編譯結果如下: 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)通過反編譯的結果知:?
每個enum元素都是 LikeClasses 類型的 static final 實例;
解說2)因為它們是static實例,所以無法訪問外部類的 非static 元素或方法:?所以對于內部的enum 實例來說,其行為與一般的內部類并不相同;

【荔枝-將一個常量相關的方法關聯到選擇上,再使用 EnumSet保存客戶選擇】
// 荔枝-將一個常量相關的方法關聯到選擇上,再使用 EnumSet保存客戶選擇 public class CarWash {public enum Cycle { // enum 類UNDERBODY { // 每個enum實例編寫不同的抽象方法實現// 常量相關的方法,因為 UNDERBODY 的類型是 public static final chapter19.CarWash$Cycle UNDERBODY; (static final 是常量類)void action() { print("Spraying the underbody");}},WHEELWASH {void action() { // 常量相關的方法print("Washing the wheels");}},PREWASH {void action() { print("Loosening the dirt");}},BASIC {void action() { // 常量相關的方法print("The basic wash");}},HOTWAX {void action() { // 常量相關的方法print("Applying hot wax");}},RINSE {void action() { // 常量相關的方法print("Rinsing");}},BLOWDRY {void action() { // 常量相關的方法print("Blowing dry");}};abstract void action();}// EnumSet.of() 有很多重載方法,可以傳入多個 enum實例進行存儲EnumSet<Cycle> cycles = EnumSet.of(Cycle.BASIC, Cycle.RINSE);public void add(Cycle cycle) {cycles.add(cycle);}// 遍歷 EnumSet 中的 enum 實例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 實例// 元素被添加的順序 并不決定其 存儲順序wash.add(Cycle.BLOWDRY);wash.add(Cycle.BLOWDRY); // 重復元素被忽略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 */ /* 反編譯結果: 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)
與使用匿名內部類相比較,定義常量相關的方法的語法更高效和簡潔;
解說2)EnumSet 不存儲重復的 enum 實例,所以對同一個 enum實例調用兩次 add() 方法,大于等于第2次的add()方法調用都會被忽略;
解說3)向EnumSet 添加元素的順序并不能決定其在 EnumSet中的存儲順序,其存儲順序 決定于 enum 實例定義時的順序;

4)覆蓋 enum實例的常量相關的方法;因為 enum實例的類型是 static final enum類型,靜態常量枚舉類型;


【荔枝-覆蓋 enum實例的常量相關的方法】

// 荔枝-覆蓋 enum實例的常量相關的方法 public enum OverrideConstantSpecific {NUT, // 默認方法BOLT, // 默認方法 WASHER { // 重載方法// 這是赤裸裸的覆蓋 常量相關的方法啊。@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的職責鏈(職責鏈設計模式)
1)職責鏈設計模式:
當一個請求到來時,它遍歷這個鏈,直到鏈中的某個解決方法能夠處理該請求;


【荔枝-使用enum實現職責鏈】

/* 荔枝-使用enum實現職責鏈 */ class Mail {// The NO's lower the probability of random selection:enum GeneralDelivery {YES,NO1,NO2,NO3,NO4,NO5} // 枚舉實例序列enum Scannability {UNSCANNABLE,YES1,YES2,YES3,YES4} // 枚舉實例序列enum Readability {ILLEGIBLE,YES1,YES2,YES3,YES4} // 枚舉實例序列enum Address {INCORRECT,OK1,OK2,OK3,OK4,OK5,OK6} // 枚舉實例序列enum ReturnAddress {MISSING,OK1,OK2,OK3,OK4,OK5} // 枚舉實例序列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;}// 產生一個包含隨機enum實例的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 對象的迭代器(遍歷下一個元素,通過調用randomMail() 來實現。 )public static Iterable<Mail> generator(final int count) {return new Iterable<Mail>() { // 匿名內部類int n = count;public Iterator<Mail> iterator() {return new Iterator<Mail>() {public boolean hasNext() { return n-- > 0; }public Mail next() { return randomMail(); } // 產生一個包含隨機enum實例的Mail對象public void remove() { // Not implementedthrow new UnsupportedOperationException();}};}};} } // PostOffice類內部 封裝了多個枚舉類 public class PostOffice {enum MailHandler {GENERAL_DELIVERY {@Overrideboolean handle(Mail m) { // 3.這是重寫 enum 常量相關的方法switch(m.generalDelivery) {case YES:print("Using general delivery for " + m);return true;default: return false;}}},MACHINE_SCAN {@Overrideboolean handle(Mail m) { // 3.這是重寫 enum 常量相關的方法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 常量相關的方法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 常量相關的方法switch(m.returnAddress) {case MISSING: return false;default:print("Returning " + m + " to sender");return true;}}};abstract boolean handle(Mail m); // 抽象方法}// 2.靜態方法static void handle(Mail m) {for(MailHandler handler : MailHandler.values())// 調用被覆蓋的常量相關的方法,如果能夠處理(返回true),則停止遍歷職責鏈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()); // 調用 Mail.toString() 方法handle(mail); // 1.靜態方法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 對象是 職責鏈無法解決的。 ***** Mail 9, General Delivery: NO1, Address Scanability: UNSCANNABLE, Address Readability: YES2, Address Address: OK1, Return address: OK4 Delivering Mail 9 normally ***** */ 【代碼解說】
解說1)
以上職責鏈由 enum MailHandler實現,而 enum定義的枚舉實例次序決定了各個策略在應用時的次序;

【19.10.2】使用enum的狀態機(狀態設計模式:如成都各大高校中的米源自動售貨機)
1)枚舉類型非常適合用來創建狀態機:?一個狀態機有有限個狀態。?
狀態機通常根據輸入,從一個狀態轉移到下一個狀態。也有瞬時狀態,一旦任務結束,狀態機就離開瞬時狀態;


【狀態機荔枝-自動售貨機】

// 狀態機荔枝-自動售貨機(枚舉類和枚舉實例) public enum Input {NICKEL(5), DIME(10), QUARTER(25), DOLLAR(100),TOOTHPASTE(200), CHIPS(75), SODA(100), SOAP(50),ABORT_TRANSACTION {public int amount() { // 重寫常量相關的方法throw new RuntimeException("ABORT.amount()");}},STOP { public int amount() { // 重寫常量相關的方法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)];} } ///:~ // 荔枝-自動販賣機開始 package chapter19; import java.io.File; import java.util.*;import net.mindview.util.*; import static chapter19.Input.*; // 靜態導入,主要枚舉實例無需枚舉類修飾 import static net.mindview.util.Print.*;enum Category {// 枚舉實例序列: MONEY是Category的枚舉實例,而NICKEL等是 Input的枚舉實例MONEY(NICKEL, DIME, QUARTER, DOLLAR), // 把MONEY NICKEL, DIME, QUARTER, DOLLAR 作為 EnumMap的4個key,而把MONEY 作為值(4個鍵對應一個值)插入到EnumMap中ITEM_SELECTION(TOOTHPASTE, CHIPS, SODA, SOAP),QUIT_TRANSACTION(ABORT_TRANSACTION),SHUT_DOWN(STOP);private Input[] values;Category(Input... types) { // 構造方法values = types; // 這里的values 就是插入到 EnumMap的values} // 使用 EnumMap 作為枚舉實例的容器private static EnumMap<Input,Category> categories = new EnumMap<Input,Category>(Input.class);static { // 靜態塊for(Category c : Category.class.getEnumConstants())for(Input type : c.values)categories.put(type, c); // 把Input枚舉實例作為鍵 和 把Category枚舉實例作為值插入到EnumMap中}public static Category categorize(Input input) {return categories.get(input); // 在EnumMap中獲取以input枚舉實例作為鍵的 Category 值;} } // 荔枝-自動販賣機 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 這一個枚舉實例; enum State { // State 枚舉類聲明開始RESTING { // 枚舉實例,并重寫State枚舉類的next()方法void next(Input input) {// Category.categorize(input): 在EnumMap中獲取以input枚舉實例作為鍵的 Category 值switch(Category.categorize(input)) {case MONEY:amount += input.amount();state = ADDING_MONEY;break;case SHUT_DOWN:state = TERMINAL; // 停止default:}}}, ADDING_MONEY { // 枚舉實例,并重寫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:}}}, // 調用 State 有參構造方法DISPENSING(StateDuration.TRANSIENT) { // 枚舉實例,并重寫State枚舉類的next()方法void next() {print("here is your " + selection);amount -= selection.amount();state = GIVING_CHANGE;}},GIVING_CHANGE(StateDuration.TRANSIENT) { // 枚舉實例,并重寫State枚舉類的next()方法void next() {if(amount > 0) {print("Your change: " + amount);amount = 0;}state = RESTING;}}, TERMINAL { void output() { print("Halted"); } }; // 枚舉實例,并重寫State枚舉類的output()方法private boolean isTransient = false; // 是否是瞬時狀態State() {} // State 無參構造方法State(StateDuration trans) { isTransient = true; } // State 有參構造方法(只有調用有參的State構造器,才會進入瞬時狀態)void next(Input input) { // 枚舉實例不重寫枚舉類State的 next(Input input) 方法就拋異常throw new RuntimeException("Only call " +"next(Input input) for non-transient states");}void next() { // 枚舉實例不重寫枚舉類State的 next() 方法就拋異常throw new RuntimeException("Only call next() for " +"StateDuration.TRANSIENT states");}void output() { print(amount); }} // State 枚舉類聲明結束static void run(Generator<Input> gen) { // VendingMachine 的靜態方法while(state != State.TERMINAL) {state.next(gen.next()); // gen.next() 返回 Input 枚舉類中名為 input.next()=文件中的下一個字符串 的Input類型的枚舉實例while(state.isTransient) // 只要state 不是 DISPENSING 或 GIVING_CHANGE 枚舉實例,state.isTransient始終未falsestate.next();state.output(); // 只有 TERMINAL State枚舉實例重寫了 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) { // 構造器input = new TextFile(fileName, ";").iterator();}public Input next() { // 隨機輸入生成器的next()方法if(!input.hasNext())return null;// Enum.valueOf(Input.class, input.next().trim()) 返回 Input 枚舉類中名為 input.next()=文件中的下一個字符串 的Input類型的枚舉實例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 下面是售貨機的操作類型列表 QUARTER; QUARTER; QUARTER; CHIPS; DOLLAR; DOLLAR; TOOTHPASTE; QUARTER; DIME; ABORT_TRANSACTION; QUARTER; DIME; SODA; QUARTER; DIME; NICKEL; SODA; ABORT_TRANSACTION; STOP; */ 【19.11】多路分發
1)如運算表達式 a.plus(b):?
a 和 b的數據類型都未知,如何讓a和b參與運算呢?即要執行的操作包含了不止一個類型未知的對象,這里是有兩個類型未知的對象(a和b),
而java動態綁定機制只能處理其中一種類型,這無法解決這個問題。所以必須自定義動態綁定行為;
2)解決上面的問題是多路分發:?如果要處理多個不同的類型體系,就需要為每個類型體系執行一個方法調用。

【荔枝-使用 enum 實現二路分發的荔枝(經典荔枝-基于enum的二路分發)】
// 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.*; // 靜態導入// 荔枝-使用 enum 實現二路分發的荔枝 // Item 接口 interface Item {Outcome compete(Item it);Outcome eval(Paper p);Outcome eval(Scissors s);Outcome eval(Rock r); } // Item 接口實現類 class Paper implements Item {// public enum Outcome { WIN, LOSE, DRAW } // 贏,輸,平局 ( Outcome 枚舉類型實例)public Outcome compete(Item it) { return it.eval(this); } // 返回 Outcome 枚舉類型實例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 接口實現類 class Scissors implements Item {public Outcome compete(Item it) { return it.eval(this); } // 返回 Outcome 枚舉類型實例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 接口實現類 class Rock implements Item {public Outcome compete(Item it) { return it.eval(this); } // 返回 Outcome 枚舉類型實例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() { // 隨機創建一個Item的實現類switch(rand.nextInt(3)) { // rand.nextInt(3) 取隨機數 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)); // 相應的Item接口實現類的 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是這幾種方法類型的接口,一共有三種分發類型,
所以你會看到 eval() 方法被重載了9次,因為3*3,每兩種類型的比較就可以確定一個eval()的重載版本;
解說2)可以看到,在調用 a.compete(b) 時,a和b的類型都是 Item,但具體是什么類型未知。但是通過 a 和 b的組合就可以知道調用哪一個 compete() 和 eval() 重載版本的方法;
解說3)多路分發的好處:在于保持方法調用時的優雅語法,避免了在一個方法中判定多個對象的類型的丑陋代碼;你只需要說:嘿,你們兩個,我不在乎你們是什么類型,請自行交流; (不能再干貨-基于enum和多態的多路分發荔枝)

【19.11.1】使用enum實現多路分發
1)使用構造器來初始化每個 enum 實例,并以一組結果作為參數;它們的組合就形成了類似查詢表的結構:


【荔枝-使用enum實現多路分發】

// 荔枝-使用enum實現多路分發 public enum RoShamBo2 implements Competitor<RoShamBo2> {// enum實例序列,調用RoShamBo2的構造器PAPER(DRAW, LOSE, WIN), //DRAW, LOSE, WIN分布賦值給 vPAPER, vSCISSORS, vROCKSCISSORS(WIN, DRAW, LOSE), // 同理ROCK(LOSE, WIN, DRAW); // 同理private Outcome vPAPER, vSCISSORS, vROCK; // Outcome類型的枚舉實例// RoShamBo2枚舉類的構造方法RoShamBo2(Outcome paper,Outcome scissors,Outcome rock) {this.vPAPER = paper; // 給Output實例賦值this.vSCISSORS = scissors; // 給Output實例賦值this.vROCK = rock; // 給Output實例賦值} // 重寫 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() 來隨機選取public static <T extends Enum<T>> T random(Class<T> ec) {// 通過 Class.getEnumConstants()方法取得所有 enum 實例return random(ec.getEnumConstants());}public static <T> T random(T[] values) {return values[rand.nextInt(values.length)]; // 隨機選取} } // /:~ 【代碼解說】
解說1)
在compete() 分發中,只要a和b的類型確定下來,就可以找到唯一的case,從而返回執行結果;
解說2) match()方法的返回值類型是 <T extends Competitor<T>>;而play()方法的返回值類型是? <T extends Enum<T> & Competitor<T>> 注意是 <T extends Enum<T> 類型且 Competitor<T>類型;

【19.11.2】使用常量相關的方法實現多路分發
1)把enum 用在 switch語句中實現多路分發,如下:


【荔枝-把enum 用在 switch語句中實現多路分發】

// 荔枝-把enum 用在 switch語句中實現多路分發 public enum RoShamBo3 implements Competitor<RoShamBo3> {PAPER {public Outcome compete(RoShamBo3 it) {switch(it) {default: // To placate the compilercase PAPER: return DRAW;case SCISSORS: return LOSE;case ROCK: return WIN;}}},SCISSORS {public Outcome compete(RoShamBo3 it) {switch(it) {default:case PAPER: return WIN;case SCISSORS: return DRAW;case ROCK: return LOSE;}}},ROCK {public Outcome compete(RoShamBo3 it) {switch(it) {default:case PAPER: return LOSE;case SCISSORS: return WIN;case ROCK: return DRAW;}}};public abstract Outcome compete(RoShamBo3 it);public static void main(String[] args) {// 通過反射機制 RoShamBo3.class.getEnumConstants() 可以獲得所有的枚舉實例RoShamBo.play(RoShamBo3.class, 20); } } /* Same output as RoShamBo2.java *///:~ /* 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 */ /* 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));} } ///:~ */ /* // 荔枝-隨機選取 enum 實例 public class Enums {private static Random rand = new Random(47);// 通過反射 Class.getEnumConstants() 來隨機選取public static <T extends Enum<T>> T random(Class<T> ec) {// 通過 Class.getEnumConstants()方法取得所有 enum 實例return random(ec.getEnumConstants());}public static <T> T random(T[] values) {return values[rand.nextInt(values.length)]; // 隨機選取} } // /:~ */ 【荔枝-再把RoShamBo3壓縮一下】
// 荔枝-再把RoShamBo3壓縮一下 // 把enum用于switch語句實現多路分發 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);}};// 執行第二次分發// 該方法執行一系列比較,其行為類似于 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 分發
1)使用 EnumMap能夠實現 真正的 兩路分發;


【荔枝-使用 EnumMap能夠實現 真正的 兩路分發】

package chapter19; import java.util.*; import static chapter19.Outcome.*; // 靜態導入 Outcome 枚舉實例// public enum Outcome { WIN, LOSE, DRAW } // 贏,輸,平局 // 荔枝-使用 EnumMap能夠實現 真正的 兩路分發 enum RoShamBo5 implements Competitor<RoShamBo5> {PAPER, SCISSORS, ROCK;// 定義 EnumMap 類型 變量static EnumMap<RoShamBo5,EnumMap<RoShamBo5,Outcome>> table = new EnumMap<RoShamBo5, EnumMap<RoShamBo5,Outcome>>(RoShamBo5.class);static { // 靜態塊for(RoShamBo5 it : RoShamBo5.values())// 填充 EnumMap容器,其中 RoShamBo5 枚舉實例作為鍵,EnumMap作為值;table.put(it, new EnumMap<RoShamBo5,Outcome>(RoShamBo5.class));initRow(PAPER, DRAW, LOSE, WIN);initRow(SCISSORS, WIN, DRAW, LOSE);initRow(ROCK, LOSE, WIN, DRAW);} // 把 RoShamBo5枚舉實例作為鍵,static void initRow(RoShamBo5 it, Outcome vPAPER, Outcome vSCISSORS, Outcome vROCK) {EnumMap<RoShamBo5,Outcome> row = RoShamBo5.table.get(it); // 獲取 EnumMap容器(參見table容器的填充語句)// 以 RoShamBo5 枚舉實例為鍵,OutCome 枚舉實例作為值 填充 EnumMap容器row.put(RoShamBo5.PAPER, vPAPER);row.put(RoShamBo5.SCISSORS, vSCISSORS);row.put(RoShamBo5.ROCK, vROCK);}public Outcome compete(RoShamBo5 it) {return table.get(this).get(it);}public static void main(String[] args) {RoShamBo.play(RoShamBo5.class, 20);} } /* Same output as RoShamBo2.java *///:~ /* 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 */ 【19.11.4】使用二維數組
1)進一步簡化實現兩路分發的解決方案。(這種方法很可能也是最快速的,因為EnumMap內部是基于數組實現的)


【荔枝-使用二維數組實現二路分發】

// 荔枝-使用二維數組實現二路分發 enum RoShamBo6 implements Competitor<RoShamBo6> {PAPER, SCISSORS, ROCK; // RoShamBo6 枚舉實例private static Outcome[][] table = { // Outcome 枚舉實例二維數組{ DRAW, LOSE, WIN }, // PAPER{ WIN, DRAW, LOSE }, // SCISSORS{ LOSE, WIN, DRAW }, // ROCK};public Outcome compete(RoShamBo6 other) {return table[this.ordinal()][other.ordinal()]; // ordinal 為該枚舉實例的定義順序 }public static void main(String[] args) {RoShamBo.play(RoShamBo6.class, 20);} } ///:~ /* 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 */? ?【19.12】總結
?1)
java中的枚舉比 C 胡 C++ 中的enum 更加成熟;
?2)本章正好說明了 枚舉所能帶來的價值。?有時,恰恰因為它,你才可以優雅而干凈地解決問題;
?3)java 1.0 對 術語 enumeration 的選擇正式一個不幸的反例。?對于一個專門用于從序列中選擇每一個元素的對象而言,java 1.0竟然沒有使用 俗語 iterator來表示。()
?4)java 的后序版本修正了這個錯誤。?但是 Enumeration 接口已經無法輕易抹去;



總結

以上是生活随笔為你收集整理的thinking-in-java(19)枚举类型的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。