Enum 枚舉類


Enum 枚舉類

基礎

定義與用途

枚舉類型是Java 5中新增特性的一部分,它是一種特殊的數據類型,之所以特殊是因為它既是一種類(class)類型卻又比類類型多了些特殊的約束,但是這些約束的存在也造就了枚舉類型的簡潔性、安全性以及便捷性。

public abstract class Enum<E extends Enum >
extends Object
implements Comparable , Serializable

枚舉可以看出是約束更嚴格的普通類,考慮一下如果要表現一個有序集合的內容我們可以如何定義,比如定義一周的七天,一般我們可以這么定義

//定義一周七天
public class Day {

    public static final int MONDAY =0;

    public static final int TUESDAY=1;

    public static final int WEDNESDAY=2;

    public static final int THURSDAY=3;

    public static final int FRIDAY=4;

    public static final int SATURDAY=5;

    public static final int SUNDAY=6;

}

但實際上在安全性和間接性上面都比較差。所以當面對需要定義一個有限集合時我們可以使用枚舉類

基本方法

構造方法:
protected Enum(String name,int ordinal)
唯一的構造函數。 程序員無法調用此構造函數。 它由編譯器響應枚舉類型聲明發出的代碼使用。
方法摘要:

protected Object clone()
拋出CloneNotSupportedException。
int compareTo(E o)
將此枚舉與指定的對象進行比較以進行訂購。
boolean equals(Object other)
如果指定的對象等於此枚舉常量,則返回true。
protected void finalize()
枚舉類不能有finalize方法。
getDeclaringClass()
返回與此枚舉常量的枚舉類型相對應的Class對象。
int hashCode()
返回此枚舉常量的哈希碼。
String name()
返回此枚舉常量的名稱,與其枚舉聲明中聲明的完全相同。
int ordinal()
返回此枚舉常數的序數(其枚舉聲明中的位置,其中初始常數的序數為零)。
String toString()
返回聲明中包含的此枚舉常量的名稱。
static <T extends Enum >T valueOf(class enumType, String name)
返回具有指定名稱的指定枚舉類型的枚舉常量。
static <T extends Enum >T[] values()
返回枚舉內容數組
Class getDeclaringClass()
返回與此枚舉常量的枚舉類型相對應的 Class 對象

示例

package JavaCore.Enum;

public class Enum_Demo {
    public static void main( String[] args ) {
        Day day = Day.FRIDAY;
        System.out.println(day);

        System.out.println(day.getDeclaringClass());
        
        System.out.println(Enum.valueOf(Day.class, "SUNDAY"));
        System.out.println(Day.valueOf("SUNDAY"));
        
        System.out.println(Day.FRIDAY.name());

        for (Day d:Day.values()) System.out.println(d.name());
    }
}
enum Day {
    MONDAY,TUESDAY,WEDNESDAY,THURSDAY,SATURDAY, SUNDAY,FRIDAY
}

進階

實現原理

下面是Day反編譯的源碼是實現,
Day類(注意該類是final類型的,將無法被繼承)而且該類繼承自java.lang.Enum類,該類是一個抽象類(稍后我們會分析該類中的主要方法),除此之外,編譯器還幫助我們生成了7個Day類型的實例對象分別對應枚舉中定義的7個日期,這也充分說明了我們前面使用關鍵字enum定義的Day類型中的每種日期枚舉常量也是實實在在的Day實例對象,只不過代表的內容不一樣而已。注意編譯器還為我們生成了兩個靜態方法,分別是values()和 valueOf(),稍后會分析它們的用法,到此我們也就明白了,使用關鍵字enum定義的枚舉類型,在編譯期后,也將轉換成為一個實實在在的類,而在該類中,會存在每個在枚舉類型中定義好變量的對應實例對象,如上述的MONDAY枚舉類型對應public static final Day MONDAY;,同時編譯器會為該類創建兩個方法,分別是values()和valueOf()。摘自:
https://blog.csdn.net/javazejian/article/details/71333103

//反編譯Day.class
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/zejian/enumdemo/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
        });
    }
}

枚舉與Class對象

返回類型 方法 詳解
T[] getEnumConstants() 返回該枚舉類型的所有元素,如果Class對象不是枚舉類型,則返回null
boolean isEnum() 當且僅當該類聲明為源代碼中的枚舉時返回 true
//正常使用
Day[] ds=Day.values();
//向上轉型Enum
Enum e = Day.MONDAY;
//無法調用,沒有此方法
//e.values();
//獲取class對象引用
Class<?> clasz = e.getDeclaringClass();
if(clasz.isEnum()) {
    Day[] dsz = (Day[]) clasz.getEnumConstants();
    System.out.println("dsz:"+Arrays.toString(dsz));
}
/**
   輸出結果:
   dsz:[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
 */

自定義枚舉類和構造方法及toString()

枚舉類可以看成是個是一個約束更嚴格的普通類,他也可以有變量和方法甚至可以有main方法

package JavaCore.Enum;

import java.util.Arrays;

/**
 * 自定義枚舉類的屬性和構造函數
 */
public enum Enum_Advance_Custom {

    //枚舉的內容必須首先寫在前面,不然編譯器報錯,因為調用構造函數創建枚舉實例只能由編譯器執行,無法手動調用
    MONDAY("星期一"),
    TUESDAY("星期二"),
    WEDNESDAY("星期三"),
    THURSDAY("星期四"),
    FRIDAY("星期五"),
    SATURDAY("星期六"),
    SUNDAY("星期天");    //,逗號分隔,分號結尾

    //定義類變量desc:描述
    private String desc;

    //自定義構造方法
    Enum_Advance_Custom( String desc ) {
        this.desc = desc;
    }

    public String getDesc() {
        return desc;
    }

    //Enum的toString()可以重新但也只有toString()能夠重寫
    @Override
    public String toString() {
//        return super.toString();
        return desc;
    }

    public static void main( String[] args ) {
        for (Enum_Advance_Custom e : Enum_Advance_Custom.values()) {
            System.out.println(e.name() + "----" + e.getDesc());
        }

        Class c = Enum_Advance_Custom.FRIDAY.getDeclaringClass();
        for (Object enumConstant : c.getEnumConstants()) {
            System.out.println("getEnumConstants():" + enumConstant);
        }
        System.out.println("getEnumConstants():" + Arrays.toString(c.getEnumConstants()));
    }
}

Enum中使用抽象方法來實現枚舉實例的多態性

枚舉類中可以使用抽象方法來使每個實現抽象方法的實例產生不同的行為方式,使得枚舉類具備多態的屬性,但是請注意這只是多態的屬性不是多態也就是只有定義在枚舉類中的具體的枚舉實例有不同的行為,枚舉的實例是編譯器實例的不是程序員,所以枚舉不能像普通類一樣傳入不同的參數來實現多態。

package JavaCore.Enum;
/**
 * 使用抽象方法來是的實現不同抽象方法的枚舉實例具備不同的行為
 */
public enum Enum_Advance_Abstract {

    FLY{
        @Override
        public String action() {
            return "I CAN FLY";
        }
    },
    WALK {
        @Override
        public String action() {
            return "I CAN WALK";
        }
    },
    SWIM {
        @Override
        public String action() {
            return "I CAN SWIM";
        }
    };

    public abstract String action();

    public static void main( String[] args ) {
        for (Enum_Advance_Abstract e: Enum_Advance_Abstract.values()) System.out.println(e.action());
    }
}

Enum與接口

Enum是個抽象類其實現類enum繼承了Enum進而enum類不能再繼承其他類,但enum是可以實現多接口的

package JavaCore.Enum;

/**
 * 枚舉類實現接口
 * 這個示例使用了一個枚舉類套實現接口的枚舉類來將事物根據其運動方式來進行做了一個列表
 */
public enum Enum_Advance_Interface {

    REPTILIA(Run.reptilia.class),
    BIRD(Run.birds.class)
    ;

    private Run[] values;

    private Enum_Advance_Interface( Class<? extends Run> kind ) {

        //通過Class來獲取對象枚舉實例
        values = kind.getEnumConstants();

    }

    public static void main( String[] args ) {
        for (Enum_Advance_Interface e:Enum_Advance_Interface.values()) System.out.println(e);
    }

}

interface Run {
    void move();

    enum reptilia implements Run{
        MAN,
        DOG,
        CAT,
        BIG;

        @Override
        public void move() {
            System.out.println("walk");
        }
    }

    enum birds implements Run {
        CROW,
        EAGLE,
        BAT;

        @Override
        public void move() {
            System.out.println("fly");
        }
    }
}

enum和switch

在JDK1.7之后枚舉類是可以直接使用在switc當中的

package JavaCore.Enum;

public class Enum_Advance_Switch {

    public static void main( String[] args ) {
        printDay(Day.MONDAY);
        printDay(Day.TUESDAY);
        printDay(Day.WEDNESDAY);
        printDay(Day.THURSDAY);
        printDay(Day.FRIDAY);
        printDay(Day.SATURDAY);
        printDay(Day.SUNDAY);
    }

    /**
     * switch語句后不加break,那么代碼會一直順序執行下去(忽略后面的case條件判斷),直到break或是end語句
     * @param day
     */
    private static void printDay(Day day) {
        switch (day) {
            case MONDAY:
                System.out.println("星期一");
                break;
            case TUESDAY:
                System.out.println("星期二");
                break;
            case WEDNESDAY:
                System.out.println("星期三");
                break;
            case THURSDAY:
                System.out.println("星期四");
                break;
            case FRIDAY:
                System.out.println("星期五");
                break;
            case SATURDAY:
                System.out.println("星期六");
                break;
            case SUNDAY:
                System.out.println("星期天");
                break;
        }
    }
}

Enum與單例

單例模式可以說是最常使用的設計模式了,它的作用是確保某個類只有一個實例,自行實例化並向整個系統提供這個實例。在實際應用中,線程池、緩存、日志對象、對話框對象常被設計成單例,總之,選擇單例模式就是為了避免不一致狀態,下面我們將會簡單說明單例模式的幾種主要編寫方式,從而對比出使用枚舉實現單例模式的優點
對比單例的優點要考慮以下幾點:
線程安全,延遲加載,序列化與反序列化安全,反射安全

  • 餓漢式單例:基於類加載機制避免了多線程同步問題,但無法延遲加載,當涉及資源很多時,會導致加載很慢增加初始化的負載
/**
 * 餓漢式(基於classloder機制避免了多線程的同步問題)
 */public class SingletonHungry {

    private static SingletonHungry instance = new SingletonHungry();

    private SingletonHungry() { }

    public static SingletonHungry getInstance() {
        return instance;
    }
}
  • 懶漢式單例:
    雙重檢驗加鎖,getSingleton()方法中,進行兩次null檢查。這樣可以極大提升並發度,進而提升性能。畢竟在單例中new的情況非常少,絕大多數都是可以並行的讀操作,因此在加鎖前多進行一次null檢查就可以減少絕大多數的加鎖操作,也就提高了執行效率。但是必須注意的是volatile關鍵字,該關鍵字有兩層語義:
    第一層是可見性,可見性是指在一個線程中對該變量的修改會馬上由工作內存(Work Memory)寫回主內存(Main Memory),所以其它線程會馬上讀取到已修改的值,關於工作內存和主內存可簡單理解為高速緩存(直接與CPU打交道)和主存(日常所說的內存條),注意工作內存是線程獨享的,主存是線程共享的。
    volatile的第二層語義是禁止指令重排序優化,我們寫的代碼(特別是多線程代碼),由於編譯器優化,在實際執行的時候可能與我們編寫的順序不同。編譯器只保證程序執行結果與源代碼相同,卻不保證實際指令的順序與源代碼相同,這在單線程並沒什么問題,然而一旦引入多線程環境,這種亂序就可能導致嚴重問題。
public class Singleton {
    private static volatile Singleton singleton = null;
    private Singleton(){}

    public static Singleton getSingleton(){
        if(singleton == null){
            synchronized (Singleton.class){
                if(singleton == null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }    
}
  • 枚舉單例
package JavaCore.Enum;

/**
 * 枚舉單例,枚舉當中只有一個枚舉實例
 * 訪問:Enum.INSTANCE就能訪問
 * 實例化:枚舉的實例化是由編譯器來操作的,其構造方法只能由編譯器訪問
 * 序列化:舉枚序列化是由jvm保證的,每一個枚舉類型和定義的枚舉變量在JVM中都是唯一的,
 *         在枚舉類型的序列化和反序列化上,Java做了特殊的規定:在序列化時Java僅僅是將枚舉對象的name屬性輸出到結果中,
 *         反序列化的時候則是通過java.lang.Enum的valueOf方法來根據名字查找枚舉對象。編譯器是不允許任何對這種序列化機制的定制的
 *         並禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法,從而保證了枚舉實例的唯一性
 * 反射:反射無法創建枚舉的實例,枚舉的實例只能由編譯器創造,在使用reflect 包中Constructor新建實例時源碼中就明確判斷是否為枚舉類型,如果是枚舉類型則無法新建實例
 *
 * 但缺點就是,枚舉時占用的內存常常是靜態變量的兩倍還多
 */
public enum Enum_Advance_Singleton {
    INSTANCE;

    //下面如同一個正常類一樣來定義實例的屬性和方法
    private String name;

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

枚舉工具類

EnumMap

一個專門Map實現與枚舉類型鍵一起使用。 枚舉映射中的所有密鑰必須來自創建映射時明確或隱式指定的單個枚舉類型。 枚舉地圖在內部表示為數組。 這種表示非常緊湊和高效。枚舉映射以其鍵的自然順序 (枚舉枚舉常數被聲明的順序)維護。 這反映在由所述集合的視圖(返回的迭代keySet() , entrySet()和values() )。由集合視圖返回的迭代器弱一致 :它們不會拋出ConcurrentModificationException ,並且它們可能顯示或可能不顯示在迭代進行時發生的映射的任何修改的影響。不允許使用空鍵。 嘗試插入空鍵將丟失NullPointerException 。 然而,嘗試測試是否存在空鍵或刪除一個將會正常工作。 允許空值。
EnumMap不同步。 如果多個線程同時訪問枚舉映射,並且至少有一個線程修改映射,則應該在外部進行同步。 這通常通過在自然地封裝枚舉映射的某些對象上進行同步來實現。 如果沒有此類對象存在,則應使用Collections.synchronizedMap(java.util.Map<K, V>)方法“包裝”地圖。 這最好在創建時完成,以防止意外的不同步訪問:
Map<EnumKey, V> m = Collections.synchronizedMap(new EnumMap<EnumKey, V>(...));

EnumSet

一個專門Set實現與枚舉類型一起使用。 枚舉集中的所有元素都必須來自創建集合時明確或隱式指定的單個枚舉類型。 枚舉集在內部表示為位向量。 這種表示非常緊湊和高效。 這個課程的空間和時間表現應該足夠好,可以將其作為基於傳統int的“位標志”的高品質,類型安全的替代品使用 。 即使批量操作(如containsAll和retainAll )也應該很快運行,如果他們的參數也是枚舉集。
由iterator返回的迭代器以自然順序 (枚舉枚舉常量的聲明順序)遍歷元素。 返回的迭代器是弱一致的 :它永遠不會拋出ConcurrentModificationException ,它可能顯示或可能不顯示在迭代進行中發生的集合的任何修改的影響。不允許使用零元素。 嘗試插入一個空元素將拋出NullPointerException 。 然而,嘗試測試null元素的存在或刪除一個將會正常工作。
EnumSet不同步。 如果多個線程同時訪問枚舉集,並且至少有一個線程修改該集合,則它應該在外部同步。 這通常通過在自然地封裝枚舉集的一些對象上進行同步來實現。 如果沒有這樣的對象存在,那么該組件應該使用Collections.synchronizedSet(java.util.Set )方法“包裝”。 這最好在創建時完成,以防止意外的不同步訪問: Set s = Collections.synchronizedSet(EnumSet.noneOf(MyEnum.class));

本博客為Swagger-Ranger的筆記分享,文中源碼地址: https://github.com/Swagger-Ranger
歡迎交流指正,如有侵權請聯系作者確認刪除: liufei32@outlook.com


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM