目錄html
本文轉載自博客 - Java枚舉類型, 博主對原文內容及結構做了必定的修改.java
修改時參考到的文章:深刻理解Java枚舉類型(enum).數組
從JDK 5開始, Java中多了一個關鍵字 —— enum
: 能夠將一組具備名稱的值(包括String、Integer等)的有限集合建立爲一種新的類型, 而這些具名的值能夠做爲常規的程序組件使用.dom
這些具名的值稱爲枚舉值, 這種新的類型稱爲枚舉類型.ide
下面是一個簡單的表示星期幾的枚舉類:函數
enum Day { // 結尾處能夠不用「;」, 但如有其餘方法, 必須經過「;」結束枚舉實例的聲明 SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY }
建立enum時, 編譯器會自動爲enum類添加一些特性, 好比:工具
a) 建立
toString()
方法: 以便顯示某個枚舉實例的名字;測試b) 建立
name()
方法: 獲取枚舉類型中某個實例的名稱;thisc) 建立
ordinal()
方法: 表示某個特定枚舉常量的申明順序, 從0開始;.netd) 建立
values()
方法: 按照枚舉常量的申明順序產生這些常量構成的數組…...
關於這些特性, 編寫測試方法以下:
public class EnumTest { public static void main(String[] args) { // 獲取枚舉類型的父類型 System.out.println(Day.class.getSuperclass()); // 遍歷枚舉類型中的值 for (Day day : Day.values()) { System.out.println(day.name() + ", ordinal: " + day.ordinal()); } } }
程序的輸出結果是:
class java.lang.Enum // 全部enum的父類都是它 SUNDAY, ordinal: 0 // ordinal()方法從0開始計數 MONDAY, ordinal: 1 TUESDAY, ordinal: 2 WEDNESDAY, ordinal: 3 THURSDAY, ordinal: 4 FRIDAY, ordinal: 5 SATURDAY, ordinal: 6
從輸出結果能夠看到: 枚舉類型Day的父類是java.lang.Enum
——咱們的代碼中並無指明extends
動做, 因此這是由編譯器完成的.
(1) 利用javac
編譯EnumTest.java
文件後, 會生成Day.class
和EnumTest.class
文件, 這裏的Day.class
就是枚舉類型 —— 使用關鍵字enum
定義枚舉類型並編譯後, 編譯器會自動生成一個與枚舉相關的類;
(2) 反編譯查看Day.class
文件:
// final修飾的類, 不容許再被繼承 final class Day extends Enum { // 編譯器添加的靜態values()方法 public static Day[] values() { return (Day[])$VALUES.clone(); } // 編譯器添加的靜態valueOf()方法 —— 間接調用了Enum類的valueOf()方法 public static Day valueOf(String s) { return (Day)Enum.valueOf(com/healchow/java/Day, s); } // 私有構造函數, 不容許被外部調用 private Day(String s, int i) { super(s, i); } // 前面定義的7種枚舉實例, 都是靜態常量 public static final Day MONDAY; public static final Day TUESDAY; public static final Day WEDNESDAY; public static final Day THURSDAY; public static final Day FRIDAY; public static final Day SATURDAY; public static final Day SUNDAY; private static final Day $VALUES[]; static { // 實例化枚舉實例, 並指定聲明的次序 MONDAY = new Day("MONDAY", 0); TUESDAY = new Day("TUESDAY", 1); WEDNESDAY = new Day("WEDNESDAY", 2); THURSDAY = new Day("THURSDAY", 3); FRIDAY = new Day("FRIDAY", 4); SATURDAY = new Day("SATURDAY", 5); SUNDAY = new Day("SUNDAY", 6); $VALUES = (new Day[] { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }); } }
(3) 編譯器生成的靜態方法:
①
values()
: 獲取枚舉類中的全部變量, 並做爲數組返回;
②valueOf(String name)
: 與Enum類中的valueOf()
的做用相似 —— 根據名稱獲取枚舉變量, 只不過編譯器生成的valueOf()
更簡潔: 只需傳遞一個參數.==> 這兩個方法是編譯器自動加入的, Enum類中並無, 因此若是咱們將枚舉實例向上轉型爲Enum, 那這兩個方法就沒法被調用了.
(4) 枚舉類不能繼承其餘任何類:
因爲枚舉類已經繼承了java.lang.Enum
(是一個抽象類), 而Java又不支持多繼承, 因此enum
不能再繼承其餘的類, 可是能實現接口. —— 這在反編譯後的信息中能夠看到, 編譯器爲enum生成的class被final修飾, 也就是不容許被繼承.
==> 因此, enum
只是看起來像一種新的數據類型, 除了上面講到的這些特殊的編譯行爲, 並無什麼特殊的地方.
除了不能繼承一個enum
外, 基本上能夠把enum
看成一個普通的類來處理, 也就是說能夠向enum
中添加方法, 好比返回其自身描述的方法, 還能夠添加main方法.
下面是一個演示enum
添加自定義方法和實現接口的示例:
(1) 定義一個對象描述信息的接口:
interface ObjectDescription { String todo(); }
(2) 建立枚舉類:
public enum Signal implements ObjectDescription { // 結尾處能夠不用「;」, 但如有其餘方法, 必須經過「;」結束枚舉實例的聲明 Red("紅燈", "敢過去就是6分, 還要罰款哦"), Yellow("黃燈", "黃燈你也不敢過"), Green("綠燈", "綠燈也得當心過啊"); // 其餘屬性、方法都必須定義在枚舉實例的聲明以後, 不然編譯器將報錯 private String name; private String description; /** * 構造方法, 對內部變量進行初始化 */ Signal(String name, String description) { this.name = name; this.description = description; } public String getName() { return name; } public String getDescription() { return description; } @Override public String todo() { return "Signal類用於表示交通訊號燈, [" + this + "] 表示 [" + this.getName() + "]"; } public static void main(String[] args) { // 調用實現的接口中的方法 for (Signal signal : Signal.values()) { System.out.println(signal.todo()); } // 調用自定義的方法 for (Signal signal : Signal.values()) { System.out.println(signal.getName() + ": " + signal.getDescription()); } } }
(3) 運行結果以下:
Signal類用於表示交通訊號燈, [Red] 表示 [紅燈] Signal類用於表示交通訊號燈, [Yellow] 表示 [黃燈] Signal類用於表示交通訊號燈, [Green] 表示 [綠燈] 紅燈: 敢過去就是6分, 還要罰款哦 黃燈: 黃燈你也不敢過 綠燈: 綠燈也得當心過啊
使用注意事項:
a) 若是要自定義方法, 就必須在enum實例序列的最後添加一個分號, 同時Java要求必須先定義enum實例, 不然編譯時會報錯.
b)
enum
的構造器只能是private
, 它只能在enum
內部被用來建立enum
實例, 一旦enum
定義結束, 編譯器就不容許再使用其構造器來建立任何實例了.
與常規抽象類同樣, 枚舉類容許咱們爲其定義抽象方法, 而後靈每一個枚舉實例都實現該方法, 以便產生不一樣的行爲方式.
注意:
abstract
關鍵字對於枚舉類來講並非必須的.
public enum EnumTest { // 枚舉實例的聲明必須在最前 FIRST { // 實現抽象方法 @Override public String getInfo() { return "FIRST TIME"; } }, SECOND { // 實現抽象方法 @Override public String getInfo() { return "SECOND TIME"; } } ; // 若是以後還有其餘成員, 就必須用「;」結束 /** * 定義抽象方法 * @return */ public abstract String getInfo(); // 測試 public static void main(String[] args) { System.out.println("First: " + EnumTest.FIRST.getInfo()); // First: FIRST TIME System.out.println("Second: " + EnumTest.SECOND.getInfo()); // Second: SECOND TIME } }
上述方式爲每一個枚舉實例定義了不一樣的行爲.
咱們可能注意到, 枚舉類的實例彷佛表現出了多態的特性, 惋惜的是枚舉類型的實例終究不能做爲類型傳遞使用, 就像下面的使用方式, 是不能經過編譯器的檢查的:
// 沒法經過編譯, 畢竟EnumTest.FIRST是個實例對象 public void text(EnumTest.FIRST instance){ }
沒法使用繼承限制了枚舉的使用, 好比須要用enum
表示食物, 但同時須要區分水果、點心等類型, 這個時候就沒不夠靈活了.
咱們經過在一個接口內部建立實現該接口的枚舉, 從而達到對元素進行分類組織的目的:
public interface Food { /** * 開胃菜 */ enum Appetizer implements Food { // 結尾處能夠不用「;」, 但如有其餘方法, 必須經過「;」結束枚舉實例的聲明 SALAD, SOUP, SPRING_ROLLS } /** * 主菜 */ enum MainCourse implements Food { RICE, NOODLES, VINDALOO, BEEF } /** * 甜品 */ enum Dessert implements Food { TIRAMISU, ICE_CREAM, BISCUIT, FRUIT } /** * 咖啡 */ enum Coffee implements Food { BLACK_COFFEE, DECAF_COFFEE, LATTE } }
對enum
而言, 實現接口是使其子類化的惟一方法.
經過上面的形式, 成功地完成了對不一樣食物的分組, 而且它們都是Food.
下面是一個枚舉的隨機選擇器, 是一個工具類:
public class Enums { private static Random rand = new Random(47); public static <T extends Enum<T>> T random(Class<T> enumClazz) { return random(enumClazz.getEnumConstants()); } public static <T> T random(T[] values) { return values[rand.nextInt(values.length)]; } }
結合工具類及上面Food接口的內容, 下面經過枚舉的枚舉實現一個產生隨機菜單的例子:
public enum Course { // 結尾處能夠不用「;」, 但如有其餘方法, 必須經過「;」結束枚舉實例的聲明 APPETIZER(Food.Appetizer.class), MAINCOURSE(Food.MainCourse.class), DESSERT(Food.Dessert.class), COFFEE(Food.Coffee.class); // 其餘屬性、方法都必須定義在枚舉實例的聲明以後, 不然編譯器將報錯 private Food[] values; Course(Class<? extends Food> kind) { // 返回枚舉中全部的元素, 及全部實例構成的數組, 若是kind不是枚舉返回null values = kind.getEnumConstants(); } public Food randomSelection() { return Enums.random(values); } public static void main(String[] args) { // 產生5份隨機菜單 for (int i = 0; i < 5; i++) { for (Course c : Course.values()) { Food food = c.randomSelection(); System.out.println(food + " "); } System.out.println("---------------"); } } }
下面的代碼用來驗證values()
方法是enum
自身的, 而不是繼承自父類java.lang.Enum
的:
public enum Signal implements ObjectDescription { // 結尾處能夠不用「;」, 但如有其餘方法, 必須經過「;」結束枚舉實例的聲明 Red("紅燈", "敢過去就是6分, 還要罰款哦"), Yellow("黃燈", "黃燈你也不敢過"), Green("綠燈", "綠燈也得當心過啊"); // 其餘屬性、方法都必須定義在枚舉實例的聲明以後, 不然編譯器將報錯 private String name; private String description; Signal(String name, String description) { this.name = name; this.description = description; } public String getName() { return name; } public String getDescription() { return description; } @Override public String todo() { return "Signal類用於表示交通訊號燈, [" + this + "] 表示 [" + this.getName() + "]"; } public static void main(String[] args) { Set<Method> methodSet = new HashSet<Method>(); // 獲取本類的全部方法 Method[] signalMethods = Signal.class.getMethods(); for (Method m : signalMethods) { methodSet.add(m); } // 獲取父類中的方法 Method[] superClassMethods = Signal.class.getSuperclass().getMethods(); // 去除本類中繼承的父類方法 for (Method m : superClassMethods) { methodSet.remove(m); } // 遍歷輸出本類中獨有的方法 for(Method m : methodSet) { System.out.println(m); } } }
結果以下, 其中各個字段的含義依次爲訪問權限 [是否靜態] 返回值類型的全限定名稱 方法的全限定名稱
:
public static com.healchow.Signal com.healchow.Signal.valueOf(java.lang.String) public static com.healchow.Signal[] com.healchow.Signal.values() public static void com.healchow.Signal.main(java.lang.String[]) public java.lang.String com.healchow.Signal.todo()
版權聲明
本文版權歸原做者全部, 若有侵權, 請聯繫博主, 定當當即刪除.
若要轉載, 請在文章頁面明顯位置標明原始連接, 不然一切責任自負.