面試準備之枚舉類型enum的介紹

枚舉類型enum並不是面試裏面的一個常考點,但是爲什麼要給枚舉寫一篇博客呢,因爲我簡歷上自己在項目中運用了枚舉,11月11號在面試的時候,讓我寫一個枚舉,我竟然沒有寫出來,當時就覺得很不好意思,所以這裏將自己對枚舉的重新認識記錄一下。

一.最簡單的枚舉例子

enum Status{
    NORMAL(1),STOP(0),DELETE(-1);

    public Integer getStatusInt() {
        return statusInt;
    }

    private Integer statusInt;
    Status(Integer statusInt){
        this.statusInt = statusInt;
    }
}

寫枚舉需要注意的地方就是枚舉的關鍵字enum,然後再是首先要申明的枚舉常量,在申明的枚舉常量之前不能有任何代碼,常量一般用大寫字母來寫,枚舉常量之間用逗號分割,最後用分號收尾。用這份代碼祭奠我在面試中沒有回答上的這道題。

二.枚舉的好處

枚舉在工程中隨處可進,爲什麼要用枚舉呢?枚舉是將一些離散值集中起來,用同一個枚舉類型來封裝他們,做到一處定義,處處使用。

1.安全性
安全性體現在枚舉將想要的有限的離散包含進來了,如果要是用這些值只能從枚舉裏面取,外面隨便定義的值不被接受。
2.易於維護,可讀性強

NORMAL(1),STOP(0),DELETE(-1);

給學生賦狀態值

Student student = new Student();
//通過枚舉賦值
student.setStatus(Status.DELETE.getStatusInt());
//通過直接賦值
student.setStatus(-1);

可以看出一個是直接通過枚舉賦值,一個是直接賦值,雖然枚舉賦值沒有直接賦值那麼簡潔,但是我們一眼就可以看出用枚舉賦值是什麼意思,而直接賦值的話對於剛接手的人來說沒有註解的話就是一頭霧水。如果那天這個1被用作表示其他狀態值了,還得查詢整個項目是修改這個值,如果用的是枚舉的話,就只需修改枚舉這一塊地方就好了

枚舉相對於常量的好處
有很多時候枚舉和常量可以相互替換,但是枚舉的話相比常量會帶有更多的信息,我這裏說的常量類型是指基礎類型和String,枚舉裏面可以設置多個屬性,包含的信息量要多一些,有人可能會疑問我也可以將常量設置成對象,對象裏面帶多個類型不就好了嗎?但是這樣的話雖然不能改變常量的引用對象,但是確能改變引用對象的值,所以說也不是很安全。

三.枚舉的基本介紹

創建枚舉類,其實是在背後偷偷的幫我們繼承了Enum這個抽象類的,這個抽象類是我們可以用到的
將網上其他人寫的代碼拿來一用一下
類聚類型定義

//定義枚舉類型
enum Day {
    MONDAY, TUESDAY, WEDNESDAY,
    THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

對Day進行反編譯後會看到這樣一個類:

//反編譯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
        });
    }
}

可以看到枚舉類其實是一個final類型的普通類,只是這個類繼承了Enum抽象類,並且有一個私有的構造方法,在靜態代碼塊裏就實例化了這些常量。
看一下Enum這個類給提供了哪些常用方法

返回類型 方法名稱 方法說明
int compareTo(E o) 比較此枚舉與指定對象的順序
boolean equals(Object other) 當指定對象等於此枚舉常量時,返回 true。
Class<?> getDeclaringClass() 返回與此枚舉常量的枚舉類型相對應的 Class 對象
String name() 返回此枚舉常量的名稱,在其枚舉聲明中對其進行聲明
int ordinal() 返回枚舉常量的序數(它在枚舉聲明中的位置,其中初始常量序數爲零)
String toString() 返回枚舉常量的名稱,它包含在聲明中
static<T extends Enum<T>> T static valueOf(Class<T> enumType, String name)

返回帶指定名稱的指定枚舉類型的枚舉常量

其中還有一個values,用於遍歷枚舉的,只是這個方法是在編譯的時候纔會出現在class文件裏,不會出現java類中,values用戶遍歷定義的枚舉類型所有常量。values返回的是一個數組。
其中ordinal返回的序數是根據常量擺放的順序值返回的,不是根據你定義的時候傳入值的大小擺放的,如果常量擺放的順序發生變化,這個值也也會發生變化,定義的常量放在第二位,那麼返回的就是1。

public enum EnumWeek {
    SUN("7","星期日"),
    Mon("1","星期一"),
    Tue("2","星期二"),
    WIN("3","星期三"),
    THU("4","星期四"),
    FRI("5","星期五"),
    SAT("6","星期六");


    private String no;
    private String des;
    EnumWeek(String no,String des){
         this.no=no;
         this.des=des;
    }

    @Override
    public String toString(){

        return this.no+";"+this.des;
    }

    public static void main(String[] args) {
        System.out.println(FRI.name());
        System.out.println(FRI.toString());//和valueOf輸出是一樣的
        System.out.println(EnumWeek.valueOf(SUN.name()));
        System.out.println(SUN.compareTo(FRI));
        System.out.println(SUN.ordinal());
        System.out.println("========遍歷輸出=======");
        for(EnumWeek enumWeek:EnumWeek.values()){
            System.out.println(enumWeek.toString());
        }

    }

}

輸出結果:

FRI
5;星期五
7;星期日
-5
0
========遍歷輸出=======
7;星期日
1;星期一
2;星期二
3;星期三
4;星期四
5;星期五
6;星期六


四.枚舉的基本用法


1.作爲一個存儲離散值的封裝類,定義好後,處處使用,這也是枚舉最常用的用法。
2.作爲switch的參數,switch的參數不僅可以是整數,也可以是字符串,還可以是枚舉類型。

enum Status{
    NORMAL(1),STOP(0),DELETE(-1);

    public Integer getStatusInt() {
        return statusInt;
    }

    private Integer statusInt;
    Status(Integer statusInt){
        this.statusInt = statusInt;
    }

    public static void switchTest(Status status){
         switch (status){
             case STOP:{
                 System.out.println("暫停");break;
             }
             case DELETE:{
                 System.out.println("刪除");break;
             }
             case NORMAL:{
                 System.out.println("正常");break;
             }

         }
    }

    public static void main(String[] args) {
        switchTest(Status.DELETE);
    }

}

最後輸出:刪除

3.創建單例

單例模式最重要的就是一個整個堆中只有一個這樣的對象,飢漢模式和雙重檢測都能做到在多線程的情況下創建一個對象,但是當我們使用反射的時候,是可以破壞這個單例模式的,會讓單例模式失效。但是使用枚舉創建單例的化就不會出現被破壞的情況
下面可以對比看一下
 

class SingleDoubleCheck{
    public static volatile SingleDoubleCheck singleDoubleCheck = null;

    private SingleDoubleCheck(){};

    public static SingleDoubleCheck getSingleDoubleCheck(){
        if(singleDoubleCheck==null){
            synchronized (SingleDoubleCheck.class){
                if(singleDoubleCheck==null){
                    singleDoubleCheck = new SingleDoubleCheck();
                }
            }
        }
        return singleDoubleCheck;
    }

}

enum EnumSingle{
    ISTANCE;

    /**
     * 如果有什麼屬性的化,在下面定義就好了,我這個相當於是不帶任何屬性的
     */

}


class Test{
    public static void main(String[] args) throws Exception{
        SingleDoubleCheck singleDoubleCheck = SingleDoubleCheck.getSingleDoubleCheck();
        Class<SingleDoubleCheck> singleDoubleCheckClass = SingleDoubleCheck.class;
        Constructor c0=  singleDoubleCheckClass.getDeclaredConstructor();
        c0.setAccessible(true);
        SingleDoubleCheck singleDoubleCheckTwo=(SingleDoubleCheck)c0.newInstance();
        System.out.println("SingleDoubleCheck對象:"+singleDoubleCheck+" ;"+singleDoubleCheckTwo.toString());
        System.out.println(singleDoubleCheck==singleDoubleCheckTwo);

        System.out.println("=================分割線===================");

        EnumSingle enumSingle = EnumSingle.ISTANCE;
        Class<EnumSingle> enumSingleClass = EnumSingle.class;
        Constructor c1 = enumSingleClass.getDeclaredConstructor();
        c1.setAccessible(true);
        EnumSingle enumSingleTwo = (EnumSingle)c1.newInstance();
        System.out.println("EnumSingle對象:"+enumSingle+" ;"+enumSingleTwo.toString());
        System.out.println(enumSingle==enumSingleTwo);
    }
}

最後輸出:可以看出SingleDoubleCheck雖然是雙重檢測單例模式,但是我們還可以通過反射獲取到實例,這就破壞了這個單例模式,而枚舉的化如果想用反射來實例是會報錯的

SingleDoubleCheck對象:com.example.findwork.normal.SingleDoubleCheck@7d417077 ;com.example.findwork.normal.SingleDoubleCheck@7dc36524
false
=================分割線===================
Exception in thread "main" java.lang.NoSuchMethodException: com.example.findwork.normal.EnumSingle.<init>()
	at java.lang.Class.getConstructor0(Class.java:3082)
	at java.lang.Class.getDeclaredConstructor(Class.java:2178)
	at com.example.findwork.normal.Test.main(EnumWeek.java:141)

 

五.枚舉的集合類型


1.EnumMap

EnumMap存儲的都是key是枚舉類型的值。

class EnumMapTest{
    public static void main(String[] args) {
        Map<Status,String> enumMap = new EnumMap(Status.class);
        enumMap.put(Status.DELETE,"刪除狀態");
        enumMap.put(Status.STOP,"暫停狀態");
        enumMap.put(Status.NORMAL,"正常狀態");
        for(Map.Entry<Status,String> entry:enumMap.entrySet()){
            System.out.println("輸出結果:"+entry.toString());
        }

    }
}
輸出結果:NORMAL=正常狀態
輸出結果:STOP=暫停狀態
輸出結果:DELETE=刪除狀態

既然有HashMap,爲什麼要用EnumMap,這是因爲EnumMap裏面存儲結構和HashMap不一樣,效率更高效一些,EnumMap裏面是兩個數組,一個存儲Enum類型的key值,一個存儲value值,沒有鏈表更沒有紅黑樹,這是因爲在實例化EnumMap會傳入Enum class對象,這樣EnumMap就知道enum有多少常量,有哪些枚舉常量,這樣就可以實例話key數組,存儲值的時候就可以根據enum的序列位知道這個值存在數組的哪一個位置了。

參考文章:https://www.cnblogs.com/zhanqing/p/11076646.html

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