用的不多,但用的時候僅僅簡單的使用,不太明白原理,今天就系統的學一下枚舉。參考:java編程思想。
Update:
- 枚舉可以當做數據字典來存儲,通常只要一個字段即instance本身,toString()或者name()打印的string。
- 枚舉的數據都是一個實例對象,比如 enum Test{A}中A就是一個對象,A的toString和name()的結果是“A”。而如果一個字符串為"A",可以轉為對應的枚舉實例:Test.valueOf("A")
1.簡單創建
枚舉就是一個固定的集合,內容是聲明的類。

package com.test.java.tenum; /** * 一個簡單的enum實例 * Created by Administrator on 2016/3/30. */ public enum SimpleEnumUse { NOT,MILD,MEDIUM,HOT,FLAMING } class TestSE{ public static void main(String[] args) { SimpleEnumUse medium = SimpleEnumUse.MEDIUM; System.out.println(medium); System.out.println(medium.ordinal()); } }
創建enum的時候,里面的元素就是類,默認創建了toString(),odinal()方法以及value字段。其中odinal是聲明的順序(從0開始),value就是聲明的名字。簡單的使用的話,這樣就可以了,配合switch即可。
2.深入了解
2.1基本特性
創建enum時,編譯器會為你生成一個相關的類,這個類繼承自java.lang.Enum。下面實例:

package com.test.java.tenum; /** * 了解enum特性 * Created by Administrator on 2016/3/30. */ public class EnumClass { public static void main(String[] args) { for (Shrubbery s : Shrubbery.values()) { //s繼承Enum //ordinal表示聲明的順序 System.out.print(s+" ordinal:"+s.ordinal()); //enum自動實現了Comparable接口 System.out.println(" compareTo:"+s.compareTo(Shrubbery.CRAWING)); //編譯器提供了equals和hashCode System.out.println(s.equals(Shrubbery.CRAWING)); System.out.println(s==Shrubbery.CRAWING); System.out.println(s.getDeclaringClass()); System.out.println(s.name()); System.out.println("======================"); } for (String s : "HANGING CRAWING GROUND".split(" ")) { Shrubbery shrubbery = Enum.valueOf(Shrubbery.class, s); System.out.println(shrubbery); } } } enum Shrubbery{ GROUND,CRAWING,HANGING }
ordinal()方法返回一個int值,這個每個enum實例在聲明時的次序,從0開始。可以使用==比較enum實例,編譯器自動為你提供了equals()和hashCode()方法。Enum類實現了Comparable接口和Serializable接口。
2.1.1靜態導入
在使用enum的實例的時候發現人家不是EnumClass.INSTANCE,即不是通過類名調用的,而是直接調用,看了下。原來是在包下靜態導入了。即:
import static package.EnumClass.*。
究竟顯示的修飾enum實例還是靜態導入要看代碼的復雜度,看看靜態導入是不是會讓代碼難以理解。
2.2可以在enum中添加自己的方法
enum可以添加方法和構造器。

package com.test.java.tenum; /** * 可以在enum中添加方法和構造器 * Created by Administrator on 2016/3/30. */ public enum OzWitch { WEST("Miss Gulch, aka the Wiched 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; private OzWitch(String description){ this.description = description; } public String getDescription(){ return description; } public static void main(String[] args) { for (OzWitch with : OzWitch.values()) { System.out.println(with+":"+with.getDescription()); } } }
通常,將enum的構造方法聲明private,而實際上對於它的可訪問性來說沒有什么變化,因為即使不是private也只能在enum內部使用創建enum實例。一旦enum定義結束,編譯器就不允許我們再使用其構造器來創建任何實例了。另外,必須優先聲明實例,然后聲明方法或屬性,否則編譯報錯。
2.3探究values()
經過一下檢測,values是編譯器添加到你創建的enum類的,而Enum類本身中並沒有values方法。我們可以通過Class對象獲取所有的enum實例。

package com.test.java.tenum; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.Set; import java.util.TreeSet; /** * 通過反射查看enum的values * 結果:values是編譯器添加到enum類的,而Enum類本身沒有values方法 * Created by Administrator on 2016/3/30. */ public class Reflection { public static Set<String> analyze(Class<?> enumClass){ System.out.println("-----------Analyzing "+enumClass+"-------"); System.out.println("Interfaces:"); for (Type t : enumClass.getGenericInterfaces()) { System.out.println(t); } System.out.println("Base: "+enumClass.getSuperclass()); System.out.println("Method:"); Set<String> methods = new TreeSet<>(); for (Method m : enumClass.getMethods()) { methods.add(m.getName()); } System.out.println(methods); return methods; } public static void main(String[] args){ Set<String> exploreMethods = analyze(Explore.class); Set<String> enumMethods = analyze(Enum.class); System.out.println("Explore.containsAll(Enum)?"+enumMethods.containsAll(enumMethods)); System.out.println("Explore.removeAll(Enum):"); exploreMethods.removeAll(enumMethods); System.out.println(exploreMethods); } } enum Explore{ HERE,THERE } /************************通過class獲取實例********************************/ enum Search{ HITHER,YON } class UpcastEnum{ public static void main(String[] args) { Search[] values = Search.values(); Enum e = Search.HITHER; // e.values();//編譯報錯,說明No values() in Enum for (Enum anEnum : e.getClass().getEnumConstants()) { System.out.println(anEnum); } } } class NonEnum{ public static void main(String[] args) { Class<Integer> integerClass = Integer.class; try{ for (Object en : integerClass.getEnumConstants()) { System.out.println(en); } }catch (Exception e){ System.out.println(e); } } }
2.4通過實現接口而不是繼承他類來擴展
應為所有enum類都繼承java.lang.Enum類。由於java不支持多繼承,所以你的enum不能再繼承其他類。但是可以實現多個接口。
2.5隨機選取

package com.test.java.tenum; import java.util.Random; /** * 隨機抽取的工具類 * Created by Administrator on 2016/3/30. */ public class Enums { private static Random random = new Random(47); public static <T extends Enum<T>> T random(Class<T> ec){ return random(ec.getEnumConstants()); } public static <T> T random(T[] values){ return values[random.nextInt(values.length)]; } }
2.6使用接口組織枚舉
無法從enum繼承子類有時很令人沮喪。這種需求有時源自我們希望擴展原enum中的元素,有時我們希望使用子類將一個enum中的元素進行分組。
在一個接口內部創建實現該接口的枚舉,以此將元素進行分組,可以達到將枚舉元素分類的目的。舉例來說,假設想用enum表示不同的食物,同時還希望每個enum元素仍然保持Food類型。可以這樣:

package com.test.java.tenum; /** * 使用接口組織枚舉 * Created by Administrator on 2016/3/30. */ public interface Food { //開胃食物 enum Appetizer implements Food{ SALAD,SOUP,SPRING_ROLLS; } //主菜 enum MainCourse implements Food{ LASNGE,BURRITO,PAD_THAI,LENTILS,HUMMOUS,VINDALOO; } //甜點 enum Dessert implements Food{ TIRAMISU,GELATO,BLACK_FOREST_CAKE,FRUIT,CREME_CARMEL; } //coffe enum Coffee implements Food{ BLACK_COFFEE,DECAF_COFFEE,ESPRESSO,LATTE,CAPPUCCINO,TEA,HERB_TEA; } //.... } class TypeOfFood{ public static void main(String[] args) { Food food = Food.Appetizer.SALAD; food = Food.MainCourse.PAD_THAI; } }
如果enum類型實現了Food接口,那么我們就可以將其實例向上轉型為Food,所以上例中的所有東西都是Food。
然而當你需要與一大堆類型打交道時,接口就不如enum好用。例如,你想創建一個枚舉的枚舉。那么可以創建一個新的enum,然后用其實例包裝Food中的每一個enum類。

package com.test.java.tenum; /** * 枚舉的枚舉 * Created by Administrator on 2016/3/30. */ public enum Course { APPETIZER(Food.Appetizer.class), MAINCOURSE(Food.MainCourse.class); private Food[] values; private Course(Class<? extends Food> kind){ values = kind.getEnumConstants(); } public Food randomSelection(){ return Enums.random(values); } }
在上面的程序中,每個Course實例都將其對應的Class對象作為構造器的參數。通過getEnumConstants方法,可以從該Class對象中取得某個Food子類的所有enum實例。因此,我們通過隨機可以生成一份菜單:

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.println(food); } System.out.println("-------"); } } }
在這個例子中,我們通過遍歷每個Course實例來獲得枚舉的枚舉的值。此外,還可以更簡潔:將一個enum嵌套在另一個enum內。

package com.test.java.tenum; /** * 枚舉嵌套 * Created by Administrator on 2016/3/30. */ public enum SecurityCategory { STOCK(Security.Stock.class), BOND(Security.Bond.class); Security[] values; SecurityCategory(Class<? extends Security> kind){ values = kind.getEnumConstants(); } 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 random = Enums.random(SecurityCategory.class); System.out.println(random+":"+random.randomSelection()); } } }
Security接口的作用是將其所包含的enum組成一個公共類型,這一點是必要的。然后SecurityCategory才能將Security中的enum作為其構造器的參數作用,以起到組織的效果。
如果我們將這種方式應用於Food實例,結果是這樣:

/** * 同理,food */ enum Meal2{ APPETIZER(Food.Appetizer.class), MAINCOURSE(Food.MainCourse.class), DESSERT(Food.Dessert.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{ LASNGE,BURRITO,PAD_THAI,LENTILS,HUMMOUS,VINDALOO; } //甜點 enum Dessert implements Food{ TIRAMISU,GELATO,BLACK_FOREST_CAKE,FRUIT,CREME_CARMEL; } //coffe 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 meal2 : Meal2.values()) { Food food = meal2.randomSelection(); System.out.println(food); } System.out.println("----------------------------"); } } }
這僅僅是重新組織了下代碼,不過多數情況下,這種方式使你的代碼具有更清晰的結構。
2.7使用EnumSet代替標識
java se5引入EnumSet,是為了通過enum創建一種替代品,以替代傳統的基於int的“標志位”。這種標識可以用來表示某種“開/關”信息。
下面enum表示在一座大樓中,警報傳感器的安防位置:

package com.test.java.tenum; import java.util.EnumSet; /** * 醬爆傳感器位置 * Created by Administrator on 2016/3/31. */ public enum AlarmPoints { STAR1,STAR2,LOBBY,OFFICE1,OFFICE2,OFFICE3,OFFICE4,BATHROOM,UTILITY,KITCHEN } /** * 我們使用enumSet來跟蹤警報器的狀態 */ class EnumSets{ public static void main(String[] args) { EnumSet<AlarmPoints> points = EnumSet.noneOf(AlarmPoints.class);//Empty set points.add(AlarmPoints.BATHROOM); System.out.println(points); //添加多個 points.addAll(EnumSet.of(AlarmPoints.STAR1,AlarmPoints.STAR2,AlarmPoints.KITCHEN,AlarmPoints.OFFICE3)); System.out.println(points); //去除office1到office4范圍內的 points.removeAll(EnumSet.range(AlarmPoints.OFFICE1,AlarmPoints.OFFICE4)); System.out.println(points); //其他的 points = EnumSet.complementOf(points); System.out.println(points); } }
2.8 使用EnumMap
EnumMap是一種特殊的Map,它要求其中的key必須來自一個enum。由於enum本身的限制,所以EnumMap在內部可由數組實現。因此EnumMap速度很快。
下面演示了命令設計模式的用法。一般來說,命令模式首先需要一個只有單一方法的接口,然后從該接口實現具有各自不同的行為的多個子類。接下來,程序員就可以構造命令對象,並在需要的時候使用它們。

package com.test.java.tenum; import java.util.EnumMap; import java.util.Map; /** * Created by Administrator on 2016/3/31. */ public class EnumMaps { public static void main(String[] args){ EnumMap<AlarmPoints,Command> em = new EnumMap<AlarmPoints, Command>(AlarmPoints.class); em.put(AlarmPoints.KITCHEN, new Command() { @Override public void action() { System.out.println("kitchen fire!"); } }); em.put(AlarmPoints.BATHROOM, new Command() { @Override public void action() { System.out.println("bathroom alert!"); } }); for (Map.Entry<AlarmPoints, Command> entry : em.entrySet()) { System.out.println(entry.getKey()+":"); entry.getValue().action(); } try { em.get(AlarmPoints.UTILITY).action(); }catch (Exception e){ System.out.println(e); } } } interface Command{ void action(); }
與EnumSet一樣,enum實例定義時的次序決定了其在EnumMap中的順序。enum的每個實例作為一個鍵總是存在的,如果你沒有為這個鍵調用put來存入相應的值,則對應的值為null。
與常量相關的方法相比,EnumMap有一個有點,允許程序員改變值對象,而常量相關的方法在編譯器就被固定了。
2.9常量相關方法
java的enum有個特性:允許程序員為enum實例編寫方法,從而為每個enum實例賦予各自不同的行為。要實現常量相關的方法,你需要為enum定義一個或者多個abstract方法,然后為每個enum實例實現該抽象方法。如下:

package com.test.java.tenum; import java.text.DateFormat; import java.util.Date; /** * Created by Administrator on 2016/3/31. */
public enum ConstantspecificMethod { DATE_TIME{ String getInfo(){ return DateFormat.getDateInstance().format(new Date()); } }, CLASSPATH{ String getInfo(){ return System.getenv("CLASSPATH"); } }, VERSION{ String getInfo(){ return System.getProperty("java.version"); } }; abstract String getInfo(); public static void main(String[] args) { for (ConstantspecificMethod csm : values()) { System.out.println(csm.getInfo()); } } }
通過相應的enum實例,我們可以調用其上的方法。這通常也成為表驅動的代碼(table-driven code),請注意它與前面提到的命令模式的相似之處。
在面向對象的程序設計中,不同的行為與不同的類關聯。而通過常量相關的方法,每個enum實例可以具備自己獨特的行為,這似乎說明每個enum實例就是一個特殊的類。我們並不能真的將enum實例當做一個類來使用。
與使用匿名內部類相比較,定義常量相關方法的語法更高效和簡潔,下面是一個有趣的洗車的例子: