java-枚舉類

轉載自Todo_的博客http://blog.csdn.net/todo_/article/details/50588933

重要補充:(1)因爲枚舉類的枚舉成員都是public static final修飾的,所以枚舉類中的非枚舉成員(其他的成員變量),只會在初始化枚舉成員的時候被初始化一次(每個枚舉成員都會去初始化一次),這是用枚舉類實現單例模式的基礎(單個枚舉成員),(2)枚舉類中的靜態成員變量,不能在枚舉類的初始化方法中賦值使用,這是爲了防止出現賦值的死循環結構,會先初始化枚舉成員,在初始化靜態成員,(3)枚舉類成員的初始化,雖然是public static final 修飾的,但是不是在類加載的時候初始化的,而是在第一次被調用的時候,初始化的。枚舉成員實際上是枚舉類的實例對象,當枚舉類被調用的時候,枚舉類會一次性初始化所有的枚舉成員(4)枚舉類中的非枚舉成員(其他的成員變量)最好用private final 修飾,避免產生歧義。

1.枚舉類入門

Java 5 新增了一個enum關鍵字(它與class、interface關鍵字的地方相同),用以定義枚舉類。正如前面看到的,枚舉類是一種特殊的類,他一樣可以有自己的成員變量、方法,可以實現一個或多個接口,也可以定義自己的構造器。一個Java源文件中最多隻能定義一個public訪問權限的枚舉類,且java源文件也必須和該枚舉類的類名相同。

但枚舉類終究不是普通類,它與普通類有如下簡單區別。
(1)枚舉類可以實現一個或多個接口,使用enum定義的枚舉類默認繼承了java.lang.Enum類,而不是默認繼承Object類,因此枚舉類不能顯示繼承其他父類。其中java.lang.Enum類實現了java.lang.Serializable和java.lang.Comparable兩個接口。
(2)使用enum定義、非抽象的枚舉類默認會使用final修飾,因此枚舉類不能派生子類。
(3)枚舉類的構造器只能使用private訪問控制符,如果省略了構造器的訪問控制符,則默認使用private修飾;如果強制指定訪問控制符,則只能指定private修飾符。
(4)枚舉類的所有實例必須在枚舉類的第一行顯式列車,否則這個枚舉類永遠都不能產生實例。列出這些實例時,系統會自動添加public static final修飾,無須程序員顯式添加。

枚舉類默認提供了一個values()方法,該方法可以很方便地遍歷所有的枚舉值。

public enum SeasonEnum {
    // 在第一行列出4個枚舉實例
    SPRING,SUMMER,FALL,WINTER;
}

編譯上面Java程序,將生成一個SeasonEnu.class文件,這表明枚舉類型是一個特殊的Java類。由此可見,enum關鍵字和class、interface關鍵字的作用大致相似。
定義枚舉類時,需要顯示列出所有的枚舉值,如上面的SPRING,SUMMER,FALL,WINTER;所示,所有的枚舉值之間以英文逗號(,)隔開,枚舉值列舉結束後以英文分號作爲結束。這些枚舉值代表了該枚舉類的所有可能的實例。
如果需要使用該枚舉類的某個實例,則可使用EnumClass.variable的形式,如SeasonEnum.SPRING.下面我們通過一個例子來了解一下枚舉的簡單用法。

public class Test3 {

    public enum SeasonEnum {
        // 在第一行列出4個枚舉實例
        SPRING, SUMMER, FALL, WINTER;
    }

    public void judge(SeasonEnum seasonEnum) {
        // switch 語句裏的表達式可以是枚舉值
        switch (seasonEnum) {
            case SPRING :
                System.out.println("出暖花開,正好踏青");
                break;
            case SUMMER :
                System.out.println("夏日炎炎,適合游泳");
                break;
            case FALL :
                System.out.println("秋高氣爽,進補及時");
                break;
            case WINTER :
                System.out.println("冬日雪飄,圍爐賞雪");
                break;
        }
    }

    public static void main(String[] args) {
        Test3 test3 = new Test3();
        // 枚舉類默認有一個values()方法,返回該枚舉類的所有實例
        for (SeasonEnum s : SeasonEnum.values()) {
            System.out.println(s);
        }
        // 使用枚舉實例時,可通過EnumClass.variable形式來訪問
        test3.judge(SeasonEnum.SPRING);
    }
}

java.lang.Enum類

因爲所有的枚舉類都繼承了java.lang.Enum類,所以枚舉類可以直接使用java.lang.Enum類中所有包含的方法。java.lang.Enum類中提供瞭如下幾個方法:
(1)int compareTo(E o):該方法用於與指定枚舉對象比較順序,同一個枚舉實例只能與相同類型的枚舉實例進行比較。如果該枚舉對象位於指定枚舉對象之後,則返回正數;如果該枚舉對象位於指定枚舉對象之前,則返回負數,否則返回零。
(2)String name():返回此枚舉實例的名稱,這個名稱就是定義枚舉類時列出的所有枚舉值之一。與此方法相比,大多數程序員應先考慮使用toString()方法,因此toString()方法返回更加用戶友好的名稱。
(3)int ordinal():返回枚舉值在枚舉類中的索引值(就是枚舉值在枚舉聲明中的位置,第一個枚舉值的索引值爲零)。
(4):String toString():返回枚舉常量的名稱,與name方法相似,但toString()方法更常用。
(5):public static < T extends Enum < T> > T valueOf(Class< T > enumType,String name):這是一個靜態方法,用於返回指定枚舉類中指定名稱的枚舉值。名稱必須與在該枚舉類中聲明枚舉值時所用的標識符完全匹配,不允許使用額外的空白字符。

枚舉類的成員變量、方法和構造器

我們來看一個例子:

public class Test4 {

    public enum Gender {
        MALE,FEMALE;
        // 定義一個public修士的實例變量
        public String name;
    }

    public static void main(String[] args) {
        // 通過Enum的valueOf()方法來獲取指定枚舉類的枚舉值
        Gender g = Enum.valueOf(Gender.class, "FEMALE");
        // 直接爲枚舉值的name實例變量賦值
        g.name = "女";
        // 直接訪問枚舉值的name實例變量
        System.out.println(g + "代表:" + g.name);
    }
}

上面程序使用Gender枚舉類時與使用一個普通類沒有太大的差別,差別只是產生Gender對象的方式不同,枚舉類的實例只能是枚舉值,而不是隨意地通過new來創建枚舉類對象。
正如前面提到的,Java應該吧所有類設計成良好封裝的類,所以不應該允許直接訪問Gender類的name成員,而是應該通過方法來控制對name的訪問。否則可能出現混亂的情形,例如上面程序恰好設置了g.name = “女”,要是採用g.name = “男”,那程序就會非常混亂了,可能出現FEMALE代表男的局面。可以按照如下代碼來改進Gender類的設計。

public class Test4 {

    public enum Gender {
        MALE, FEMALE;
        // 定義一個public修士的實例變量
        public String name;

        public void setName(String name) {
            switch (this) {
                case MALE :
                    if (name.equals("男")) {
                        this.name = name;
                    } else {
                        System.out.println("參數錯誤");
                        return;
                    }
                    break;
                case FEMALE :
                    if (name.equals("女")) {
                        this.name = name;
                    } else {
                        System.out.println("參數錯誤");
                        return;
                    }
                    break;
            }
        }

        public String getName() {
            return name;
        }
    }

    public static void main(String[] args) {
        // 通過Enum的valueOf()方法來獲取指定枚舉類的枚舉值
        Gender g = Enum.valueOf(Gender.class, "FEMALE");
        // 直接爲枚舉值的name實例變量賦值
        g.setName("女");
        // 直接訪問枚舉值的name實例變量
        System.out.println(g + "代表:" + g.name);
        // 此時設置name值時將會提示參數錯誤
        g.setName("男");
        // 直接訪問枚舉值的name實例變量
        System.out.println(g + "代表:" + g.name);
    }
}

上面的做法可以提高安全性,但是還是不夠好。枚舉類通常應該設計成不可變類,就是說,它的成員變量值不應該允許改變,這樣會更安全,而且代碼更加簡潔。因此建議將枚舉類的成員變量使用private final 修飾。
如果所有的成員變量都是用了final修飾符來修飾,所以必須在構造器裏爲這些成員變量指定初始值(或者在定義成員變量是指定默認值,或者在初始化塊中指定初始值,但這兩種情況並不多見),因此應該爲枚舉類顯示定義帶參數的構造器。
一旦爲枚舉類顯示定義了帶參數的構造器,列出枚舉值時就必須對應地傳入參數:

我們更改上面的例子如下:

public enum Gender {
    // 此處的枚舉值必須調用對應的構造器來創建
    MALE("男"), FEMALE("女");
    // 定義一個public修士的實例變量
    private final String name;
    private Gender(String name) {
        this.name = name();
    }

    public String getName() {
        return this.name;
    }
}

實現接口的枚舉類

枚舉類也可以實現一個或多個接口。與普通類實現一個或多個接口完全一樣,枚舉類實現一個或多個接口時,也需要實現該接口所包含的方法。如下面代碼所示:

public interface GenderDesc {
     void info();
 }
public enum Gender implements GenderDesc{
       // 此處的枚舉值必須調用對應的構造器來創建
       MALE("男"), FEMALE("女");
       // 定義一個public修士的實例變量
       private final String name;
       private Gender(String name) {
           this.name = name;
       }

       public String getName() {
           return this.name;
       }

       @Override
       public void info() {
           System.out.println("這是一個用於定義性別的枚舉類");
       }
   }

與普通類實現接口完全一樣,如果由枚舉類來實現接口裏的方法,則每個枚舉值在調用這個方法時都有相同的行爲方式(因爲方法體完全一樣)。如果需要每個枚舉值在調用該方法時呈現出不同行爲方式,則可以讓每個枚舉值分別來實現該方法,每個枚舉值提供不同的實現方式,從而不同的枚舉值調用該方法是具有不同的行爲方式。代碼如下:

public interface GenderDesc {
        void info();
}
public enum Gender implements GenderDesc{
     // 此處的枚舉值必須調用對應的構造器來創建
     MALE("男")
     {
         @Override
         public void info() {
             System.out.println("這個枚舉值代表男");
         }
     }, 
     FEMALE("女")
     {
         @Override
         public void info() {
             System.out.println("這個枚舉值代表女");
         }
     };
     // 定義一個public修士的實例變量
     private final String name;
     private Gender(String name) {
         this.name = name();
     }

     public String getName() {
         return this.name;
     }
 }

這裏分別實現接口方法的語法與匿名內部類語法大致相似,只是它依然是枚舉類的匿名內部子類。

注:並不是所有的枚舉類都是用了final修飾!非抽象的枚舉類才默認使用final修飾。對於一個抽象的枚舉類而言——只要包含了抽象方法,他就是抽象枚舉類,系統默認使用abstract修飾,而不是使用final修飾。

包含抽象方法的枚舉類

假設有一個Operation枚舉類,它的4個枚舉值PLUS,MINUS,TIMES,DIVIDE分別代表加、減、乘、除四中運算,該枚舉類需要定義一個eval()方法來完成計算。
從上面描述可以看出,Operation需要讓PLUS,MINUS,TIMES,DIVIDE四個值對eval()方法各有不同的實現,此時可以考慮爲Operation枚舉類定義一個eval()抽象方法,然後讓4哥枚舉值分別爲eval()提供不同的實現,代碼如下:

package com.lyong.test;  

public enum Operation {

    PLUS {

        @Override
        public double eval(double x, double y) {
            return x + y;
        }

    },

    MINUS {

        @Override
        public double eval(double x, double y) {
            return x - y;
        }

    },

    TIMES {

        @Override
        public double eval(double x, double y) {
            return x * y;
        }

    },

    DIVIDE {

        @Override
        public double eval(double x, double y) {
            return x / y;
        }

    };

    // 爲枚舉類定義一個抽象方法
    // 這個抽象方法由不同的枚舉值提供不同的實現
    public abstract double eval(double x, double y);

    public static void main(String[] args) {
        System.out.println(Operation.PLUS.eval(3, 4));
        System.out.println(Operation.MINUS.eval(3, 4));
        System.out.println(Operation.TIMES.eval(3, 4));
        System.out.println(Operation.DIVIDE.eval(3, 4));
    }
}

編譯上面程序會生成5個class文件,其實Operation對應一個class文件,它的4個匿名內部子類分別對應一個class文件。
枚舉類裏定義抽象方法時不能使用abstract關鍵字將枚舉類定義成抽象類(因爲系統自動會爲它添加abstract關鍵字),但因爲枚舉類需要顯示創建枚舉值,而不是作爲父類,所以定義每個枚舉值時必須爲抽象方法提供實現,否則將出現編譯錯誤。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章