[轉載] Java中枚舉類型的使用 - enum

本文轉載自博客 - Java枚舉類型, 博主對原文內容及結構做了必定的修改.java

修改時參考到的文章:深刻理解Java枚舉類型(enum).數組

1 枚舉類的編譯特性

從JDK 5開始, Java中多了一個關鍵字 —— enum: 能夠將一組具備名稱的值(包括String、Integer等)的有限集合建立爲一種新的類型, 而這些具名的值能夠做爲常規的程序組件使用.dom

這些具名的值稱爲枚舉值, 這種新的類型稱爲枚舉類型.ide

1.1 枚舉類演示

下面是一個簡單的表示星期幾的枚舉類:函數

enum Day {
    // 結尾處能夠不用「;」, 但如有其餘方法, 必須經過「;」結束枚舉實例的聲明
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
}

建立enum時, 編譯器會自動爲enum類添加一些特性, 好比:工具

a) 建立toString()方法: 以便顯示某個枚舉實例的名字;測試

b) 建立name()方法: 獲取枚舉類型中某個實例的名稱;this

c) 建立ordinal()方法: 表示某個特定枚舉常量的申明順序, 從0開始;.net

d) 建立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.2 反編譯查看編譯器的操做

(1) 利用javac編譯EnumTest.java文件後, 會生成Day.classEnumTest.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只是看起來像一種新的數據類型, 除了上面講到的這些特殊的編譯行爲, 並無什麼特殊的地方.

2 向枚舉類中添加方法

除了不能繼承一個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定義結束, 編譯器就不容許再使用其構造器來建立任何實例了.

3 枚舉類中使用抽象方法

與常規抽象類同樣, 枚舉類容許咱們爲其定義抽象方法, 而後靈每一個枚舉實例都實現該方法, 以便產生不一樣的行爲方式.

注意: 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){ }

4 接口內部建立枚舉

沒法使用繼承限制了枚舉的使用, 好比須要用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.

5 枚舉類中使用枚舉

下面是一個枚舉的隨機選擇器, 是一個工具類:

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("---------------");
        }
    }
}

6 擴展: 驗證values()不是經過父類繼承的

下面的代碼用來驗證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()

版權聲明

本文版權歸原做者全部, 若有侵權, 請聯繫博主, 定當當即刪除.

若要轉載, 請在文章頁面明顯位置標明原始連接, 不然一切責任自負.

相關文章
相關標籤/搜索